/**
 * 两阶段上传管理器
 * 管理模型上传的两个阶段：
 * 1. 上传文件到缓存并解析mesh
 * 2. 检查材质库并配置材质绑定
 */

import * as THREE from 'three';
import { FBXLoader } from '../../lib/three/examples/jsm/loaders/FBXLoader.js';
import { GLTFLoader } from '../../lib/three/examples/jsm/loaders/GLTFLoader.js';
import { getMaterialLibraryManager } from './MaterialLibraryManager.js';
import { fixStringEncoding, fixModelEncoding } from '../utils/encodingFixer.js';
import { logModelUploadInfo } from '../utils/modelLogHelper.js';

export class TwoStageUploadManager {
    constructor(projectId, projectPath, uploadType = 'model') {
        this.projectId = projectId;
        this.projectPath = projectPath; // 项目文件夹名称（用于构建缓存文件路径）
        this.uploadType = uploadType; // 'model' 或 'node'
        
        // 🔧 移除共享的loader实例，每个模型解析时创建独立的loader
        // 这样可以避免并发上传时回调相互覆盖的问题
        
        this.materialLibraryManager = getMaterialLibraryManager(projectId);
        this.cacheModels = []; // 缓存中的模型列表
        this.currentStage = 0; // 0: 初始状态, 1: 第一阶段, 2: 第二阶段
        this.dialogElement = null;
        this.progressBarElement = null; // 进度条元素
        this.uploadedFiles = []; // ⭐ 记录已上传的文件名，用于检测重复
        this.maxConcurrentUploads = 5; // 🚀 最大并发上传数，默认为5
        this.currentConcurrentUploads = 0; // 🚀 当前正在上传的文件数量
        this.uploadCancelled = false; // 🚀 上传是否已取消
    }

    /**
     * 开始上传流程
     */
    async startUpload() {
        // 1. 清空缓存
        await this.clearCache();
        
        // 2. 打开第一阶段对话框
        this.showStageOneDialog();
    }

    /**
     * 清空上传缓存
     */
    async clearCache() {
        try {
            const response = await fetch('/api/cache/clear', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ projectId: this.projectId })
            });
            
            const result = await response.json();
            if (result.success) {
                this.cacheModels = [];
                this.uploadedFiles = []; // ⭐ 清空已上传文件记录
                this.uploadCancelled = false; // 🚀 重置取消标志
            } else {
                throw new Error(result.error || '清空缓存失败');
            }
        } catch (error) {
            console.error('❌ 清空缓存失败:', error);
            throw error;
        }
    }

    /**
     * 显示第一阶段对话框（文件选择和上传）
     */
    showStageOneDialog() {
        this.currentStage = 1;
        this.materialsAnalyzed = false; // 材质是否已解析
        this.hasUnanalyzedModels = false; // 是否有未解析的模型
        
        // 根据上传类型设置不同的标题
        const title = this.uploadType === 'node' ? '📤 上传节点模型组' : '📤 上传模型到模型库';
        
        const dialog = document.createElement('div');
        dialog.className = 'two-stage-upload-dialog';
        dialog.innerHTML = `
            <div class="two-stage-upload-overlay"></div>
            <div class="two-stage-upload-content">
                <div class="two-stage-upload-header">
                    <div class="header-left">
                        <h2>${title}</h2>
                    </div>
                    <div class="header-right">
                        <input type="file" id="stage-one-file-input" multiple accept=".fbx,.gltf,.glb" style="display:none">
                        <button class="btn btn-upload btn-header" id="btn-select-files">📁 选择文件</button>
                        <button class="close-btn" id="btn-close-stage-one">✕</button>
                    </div>
                </div>
                <div class="two-stage-upload-body">
                    <div class="upload-file-list" id="upload-file-list">
                        <div class="upload-empty-hint">
                            <div class="empty-icon">📁</div>
                            <p>暂无文件，请点击上方"选择文件"按钮或拖拽文件到窗口</p>
                            <p class="hint-text">支持 .fbx, .gltf, .glb 格式 | 可多次添加</p>
                        </div>
                    </div>
                    <div class="material-review-section" id="material-review-section" style="display:none;">
                        <div class="review-summary" id="review-summary"></div>
                    </div>
                </div>
                
                <!-- 全局拖拽提示层 -->
                <div class="upload-drag-overlay" id="upload-drag-overlay" style="display:none;">
                    <div class="drag-hint-content">
                        <div class="drag-icon">📤</div>
                        <p class="drag-text">拖放文件到此处上传</p>
                        <p class="drag-hint">支持 .fbx, .gltf, .glb 格式</p>
                    </div>
                </div>
                <div class="two-stage-upload-footer">
                    <div class="footer-left">
                        <button class="btn btn-analyze" id="btn-analyze-materials" style="display:none;" disabled>🔍 解析材质</button>
                        <button class="btn btn-review" id="btn-open-material-review" style="display:none;">📋 材质信息</button>
                    </div>
                    <div class="footer-right">
                        <button class="btn btn-primary" id="btn-complete-upload" style="display:none;">完成上传</button>
                    </div>
                </div>
            </div>
        `;
        
        document.body.appendChild(dialog);
        this.dialogElement = dialog;
        
        // 绑定事件
        this.bindStageOneEvents();
    }

    /**
     * 绑定第一阶段的事件
     */
    bindStageOneEvents() {
        const fileInput = document.getElementById('stage-one-file-input');
        const btnClose = document.getElementById('btn-close-stage-one');
        const btnSelectFiles = document.getElementById('btn-select-files');
        const btnAnalyze = document.getElementById('btn-analyze-materials');
        const btnReview = document.getElementById('btn-open-material-review');
        const btnComplete = document.getElementById('btn-complete-upload');
        const dragOverlay = document.getElementById('upload-drag-overlay');
        const dialogContent = this.dialogElement.querySelector('.two-stage-upload-content');

        // 选择文件按钮
        btnSelectFiles.addEventListener('click', () => {
            fileInput.click();
        });

        // 文件选择
        fileInput.addEventListener('change', (e) => {
            if (e.target.files.length > 0) {
                this.handleFilesSelected(Array.from(e.target.files));
                // 重置input，允许选择相同文件
                e.target.value = '';
            }
        });

        // ⭐ 全局拖拽监听 - 拖拽进入对话框
        let dragCounter = 0; // 用于处理 dragenter/dragleave 嵌套问题
        
        dialogContent.addEventListener('dragenter', (e) => {
            e.preventDefault();
            dragCounter++;
            
            // 检查是否是文件拖拽
            if (e.dataTransfer.types.includes('Files')) {
                dragOverlay.style.display = 'flex';
            }
        });

        dialogContent.addEventListener('dragleave', (e) => {
            e.preventDefault();
            dragCounter--;
            
            // 当完全离开对话框时隐藏提示层
            if (dragCounter === 0) {
                dragOverlay.style.display = 'none';
            }
        });

        dialogContent.addEventListener('dragover', (e) => {
            e.preventDefault();
        });

        // 拖拽提示层的 drop 事件
        dragOverlay.addEventListener('drop', (e) => {
            e.preventDefault();
            dragCounter = 0;
            dragOverlay.style.display = 'none';
            
            if (e.dataTransfer.files.length > 0) {
                this.handleFilesSelected(Array.from(e.dataTransfer.files));
            }
        });

        // 关闭按钮 - 清空缓存并关闭
        btnClose.addEventListener('click', async () => {
            await this.cancelUpload();
        });

        // 解析材质按钮
        btnAnalyze.addEventListener('click', async () => {
            await this.analyzeMaterials();
        });

        // 打开材质审核窗口按钮
        btnReview.addEventListener('click', () => {
            this.openMaterialReviewDialog();
        });

        // 完成上传按钮
        btnComplete.addEventListener('click', async () => {
            await this.completeUploadFromStageOne();
        });
    }

    /**
     * 处理选中的文件（支持并发上传）
     */
    async handleFilesSelected(files) {
        const validFiles = files.filter(file => {
            const ext = file.name.split('.').pop().toLowerCase();
            return ['fbx', 'gltf', 'glb'].includes(ext);
        });

        if (validFiles.length === 0) {
            alert('请选择有效的模型文件（.fbx, .gltf, .glb）');
            return;
        }

        // ⭐ 检测重复文件
        const duplicateFiles = [];
        const newFiles = [];
        
        for (const file of validFiles) {
            if (this.uploadedFiles.includes(file.name)) {
                duplicateFiles.push(file.name);
                console.warn(`⚠️ 队列中已存在文件: ${file.name}`);
            } else {
                newFiles.push(file);
            }
        }

        // 显示重复文件提示
        if (duplicateFiles.length > 0) {
            let message = `⚠️ 以下文件已在队列中，已自动跳过：\n\n${duplicateFiles.join('\n')}`;
            if (newFiles.length > 0) {
                message += `\n\n✅ 将添加 ${newFiles.length} 个新文件到队列`;
            }
            alert(message);
        }

        // 如果没有新文件需要添加，直接返回
        if (newFiles.length === 0) {
            return;
        }

        // ⭐ 显示文件列表 - 改为累积添加，不清空
        const fileList = document.getElementById('upload-file-list');
        
        // 如果是第一次添加文件，清空空状态提示并显示标题
        if (this.uploadedFiles.length === 0) {
            fileList.innerHTML = '<h3>上传队列：</h3>';
        }

        // 添加新文件到显示列表
        for (const file of newFiles) {
            const fileItem = document.createElement('div');
            fileItem.className = 'upload-file-item';
            fileItem.innerHTML = `
                <span class="file-name">${file.name}</span>
                <span class="file-size">${this.formatFileSize(file.size)}</span>
                <span class="file-status" data-file="${file.name}">⏳ 等待上传...</span>
            `;
            fileList.appendChild(fileItem);
            
            // 记录文件名
            this.uploadedFiles.push(file.name);
        }

        // 🚀 开始并发上传新文件
        await this.uploadFilesConcurrently(newFiles);

        // 🎯 上传完成后，更新按钮状态
        this.updateButtonsAfterUpload();
    }

    /**
     * 并发上传文件（队列 + Promise.race 实现并发控制）
     */
    async uploadFilesConcurrently(files) {
        // 重置取消标志
        this.uploadCancelled = false;
        
        const totalFiles = files.length;
        let completedCount = 0;
        let successCount = 0;
        let failedCount = 0;
        
        console.log(`🚀 开始批量上传 ${totalFiles} 个文件，最大并发数: ${this.maxConcurrentUploads}`);
        
        // 创建上传队列
        const uploadQueue = [...files];
        const activeUploads = new Map(); // 正在进行的上传任务
        
        // 并发控制循环
        while (uploadQueue.length > 0 || activeUploads.size > 0) {
            // 检查是否取消
            if (this.uploadCancelled) {
                console.warn('⚠️ 上传已取消');
                break;
            }
            
            // 填充上传槽位
            while (uploadQueue.length > 0 && activeUploads.size < this.maxConcurrentUploads) {
                const file = uploadQueue.shift();
                
                // 创建上传任务
                const uploadPromise = this.uploadFileToCache(file)
                    .then(() => {
                        successCount++;
                        console.log(`✅ [${completedCount + 1}/${totalFiles}] 上传成功: ${file.name}`);
                    })
                    .catch((error) => {
                        failedCount++;
                        console.error(`❌ [${completedCount + 1}/${totalFiles}] 上传失败: ${file.name}`, error);
                    })
                    .finally(() => {
                        completedCount++;
                        activeUploads.delete(file.name);
                    });
                
                activeUploads.set(file.name, uploadPromise);
                console.log(`📤 [${completedCount + 1}/${totalFiles}] 开始上传: ${file.name} (当前并发: ${activeUploads.size})`);
            }
            
            // 如果有活跃的上传任务，等待至少一个完成
            if (activeUploads.size > 0) {
                await Promise.race(activeUploads.values());
            }
        }
        
        // 输出统计信息
        const cancelledCount = totalFiles - completedCount;
        console.log(`🎉 批量上传完成: 总计 ${totalFiles} 个文件`);
        console.log(`   ✅ 成功: ${successCount} 个`);
        console.log(`   ❌ 失败: ${failedCount} 个`);
        if (cancelledCount > 0) {
            console.log(`   ⚠️ 取消: ${cancelledCount} 个`);
        }
    }

    /**
     * 上传完成后更新按钮状态
     */
    updateButtonsAfterUpload() {
        const btnAnalyze = document.getElementById('btn-analyze-materials');
        const btnReview = document.getElementById('btn-open-material-review');
        const btnComplete = document.getElementById('btn-complete-upload');
        const reviewSection = document.getElementById('material-review-section');

        // 标记有未解析的模型
        this.hasUnanalyzedModels = true;
        this.materialsAnalyzed = false;

        // 显示解析按钮并启用
        btnAnalyze.style.display = 'inline-block';
        btnAnalyze.disabled = false;

        // 隐藏审核按钮和完成按钮
        btnReview.style.display = 'none';
        btnComplete.style.display = 'none';
        reviewSection.style.display = 'none';
    }

    /**
     * 上传文件到缓存
     */
    async uploadFileToCache(file) {
        const statusElement = document.querySelector(`[data-file="${file.name}"]`);
        
        try {
            statusElement.textContent = '📤 上传中...';
            
            const formData = new FormData();
            formData.append('model', file);
            formData.append('originalFileName', file.name);
            formData.append('projectId', this.projectId);

            // 通过查询参数传递项目ID（用于multer destination函数）
            const response = await fetch(`/api/cache/upload?projectId=${this.projectId}`, {
                method: 'POST',
                body: formData
            });

            const result = await response.json();
            
            if (result.success) {
                statusElement.textContent = '📦 解析中...';
                
                // 解析模型的mesh结构（返回 meshMaterials 和 texturesMap）
                const parseResult = await this.parseModelMeshes(result.data);
                const meshMaterials = parseResult.meshMaterials || parseResult; // 兼容旧代码
                const texturesMap = parseResult.texturesMap || new Map();
                
                console.log(`🎨 [${file.name}] 解析完成，贴图数量:`, texturesMap.size || Object.keys(texturesMap).length);
                
                // 更新缓存中的模型信息
                await this.updateModelMeshMaterials(result.data.id, meshMaterials);
                
                // ✅ 序列化meshMaterials用于本地缓存
                const serializedMeshMaterials = {};
                for (const [meshName, meshData] of Object.entries(meshMaterials)) {
                    serializedMeshMaterials[meshName] = {
                        ...meshData,
                        textures: meshData.textures ? this.serializeTextures(meshData.textures) : null
                    };
                }
                
                // 添加到本地缓存列表
                this.cacheModels.push({
                    ...result.data,
                    meshMaterials: serializedMeshMaterials,
                    texturesMap: texturesMap  // ✅ 保存完整的贴图数据（Map对象）
                });
                
                console.log(`✅ [${file.name}] 模型缓存完成，texturesMap类型:`, texturesMap.constructor.name);
                
                statusElement.textContent = '✅ 完成';
            } else {
                throw new Error(result.error || '上传失败');
            }
        } catch (error) {
            console.error(`❌ [${file.name}] 上传/解析失败:`, error);
            statusElement.textContent = '❌ 失败: ' + error.message;
            // 重新抛出错误，让批量上传函数知道失败了
            throw error;
        }
    }

    /**
     * 解析模型的mesh结构（包含贴图信息）
     */
    async parseModelMeshes(modelData) {
        try {
            // 从缓存目录加载模型（通过静态文件服务访问）
            // 注意：现在缓存目录已经按项目分隔
            const ext = modelData.filename.split('.').pop().toLowerCase();
            // 通过后端API服务器的静态文件服务访问缓存模型文件
            // 使用完整URL确保访问正确的端口（后端服务器端口7283）
            const modelUrl = `http://localhost:7283/projects/cache/uploadmodel/${this.projectPath}/${modelData.id}.${ext}`;
            
            // 🔧 为每个模型创建独立的LoadingManager，避免并发上传时回调相互覆盖
            const loadingManager = new THREE.LoadingManager();
            
            loadingManager.onStart = (url, itemsLoaded, itemsTotal) => {
                console.log(`🎬 [${modelData.filename}] 开始加载: ${url} (${itemsLoaded}/${itemsTotal})`);
            };
            
            loadingManager.onLoad = () => {
                console.log(`✅ [${modelData.filename}] 所有资源加载完成`);
            };
            
            loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
                console.log(`⏳ [${modelData.filename}] 加载进度: ${itemsLoaded}/${itemsTotal} - ${url}`);
            };
            
            loadingManager.onError = (url) => {
                console.error(`❌ [${modelData.filename}] 加载失败: ${url}`);
            };
            
            // URL修改器：处理相对路径
            loadingManager.setURLModifier((url) => {
                console.log(`🔗 [${modelData.filename}] 原始URL: ${url}`);
                if (!url.startsWith('http') && !url.startsWith('data:') && !url.startsWith('blob:')) {
                    const baseUrl = `http://localhost:7283/projects/cache/uploadmodel/${this.projectPath}/`;
                    const newUrl = baseUrl + url;
                    console.log(`🔗 [${modelData.filename}] 转换URL: ${newUrl}`);
                    return newUrl;
                }
                return url;
            });
            
            // 加载模型
            let object3D;
            if (ext === 'fbx') {
                // 🔧 为每个FBX文件创建独立的loader实例
                const fbxLoader = new FBXLoader(loadingManager);
                
                // ✅ 等待FBX和所有贴图加载完成
                object3D = await new Promise((resolve, reject) => {
                    let fbxModel = null;
                    let loadingComplete = false;
                    
                    // 设置LoadingManager的onLoad回调
                    loadingManager.onLoad = () => {
                        console.log(`✅ [${modelData.filename}] LoadingManager - 所有资源（包括贴图）加载完成`);
                        loadingComplete = true;
                        if (fbxModel) {
                            console.log(`🎯 [${modelData.filename}] 开始解析材质和贴图`);
                            resolve(fbxModel);
                        }
                    };
                    
                    fbxLoader.load(modelUrl, 
                        (fbx) => {
                            console.log(`📦 [${modelData.filename}] 模型对象加载完成`);
                            fbxModel = fbx;
                            // 如果贴图已经全部加载完成，直接resolve
                            if (loadingComplete) {
                                console.log(`🎯 [${modelData.filename}] 开始解析材质和贴图`);
                                resolve(fbx);
                            }
                            // 否则等待LoadingManager.onLoad触发
                        }, 
                        undefined, 
                        reject
                    );
                });
            } else if (ext === 'gltf' || ext === 'glb') {
                // 🔧 为每个GLTF文件创建独立的loader实例
                const gltfLoader = new GLTFLoader(loadingManager);
                const gltf = await new Promise((resolve, reject) => {
                    gltfLoader.load(modelUrl, resolve, undefined, reject);
                });
                object3D = gltf.scene;
            } else {
                throw new Error('不支持的文件格式');
            }

            // 🎯 立即修复模型中所有对象和材质的名称编码（在解析FBX后第一时间处理）
            fixModelEncoding(object3D);

            // 收集所有mesh和材质信息（包括贴图）
            const meshMaterials = {};
            const texturesMap = new Map(); // 存储贴图信息，避免重复
            
            // 用于跟踪同名mesh的索引
            const meshNameCounts = new Map();
            
            let meshCount = 0;
            object3D.traverse((child) => {
                if (child.isMesh && child.material) {
                    meshCount++;
                    // 此时名称已经被修复过了，直接使用
                    const originalMeshName = child.name || 'Unnamed Mesh';
                    const material = child.material;
                    const materialName = material.name || 'Unnamed Material';
                    
                    // 🎯 生成稳定的mesh标识符：
                    // 如果有同名mesh，在名称后添加索引后缀（从1开始）
                    let meshName = originalMeshName;
                    if (!meshNameCounts.has(originalMeshName)) {
                        meshNameCounts.set(originalMeshName, 0);
                    } else {
                        // 同名mesh，添加索引
                        const count = meshNameCounts.get(originalMeshName) + 1;
                        meshNameCounts.set(originalMeshName, count);
                        meshName = `${originalMeshName}_${count}`;
                        
                        // ✅ 同时修改场景中mesh的名称，确保加载时能匹配
                        child.name = meshName;
                    }
                    
                    // 🎯 提取材质中的贴图信息
                    const textureInfo = this.extractTexturesFromMaterial(material);
                    
                    // 保存贴图到Map中（用于后续上传）
                    if (textureInfo) {
                        // ✅ 使用meshName作为key，因为：
                        // 1. meshName是唯一的（同名mesh已添加索引）
                        // 2. meshMaterials也使用meshName作为key
                        // 3. 确保贴图和mesh的一一对应关系
                        texturesMap.set(meshName, textureInfo);
                    }
                    
                    // 🎯 提取材质的基础属性
                    const materialProperties = this.extractMaterialProperties(material);
                    
                    // ⭐ Cache阶段：保存完整信息用于材质创建
                    // （最终保存到模型库时会简化为只有materialName）
                    meshMaterials[meshName] = {
                        materialName: materialName,  // ✅ 核心字段：材质名
                        textures: textureInfo,       // 临时：用于材质创建
                        properties: materialProperties // 临时：用于材质创建
                    };
                }
            });
            
            // ✅ 将贴图信息保存到模型数据中（用于后续预览）
            modelData.texturesMap = texturesMap;
            
            // 📊 输出模型上传时的详细信息（mesh名、材质参数、贴图名）
            logModelUploadInfo(object3D, modelData.filename, '上传模型时');
            
            // ✅ 同时返回 meshMaterials 和 texturesMap
            return { meshMaterials, texturesMap };
            
        } catch (error) {
            console.error(`❌ 解析模型失败 [${modelData.filename}]:`, error);
            // 抛出错误让上层处理，这样可以正确显示失败状态
            throw new Error(`解析失败: ${error.message}`);
        }
    }
    
    /**
     * 从材质中提取基础属性
     * @param {THREE.Material} material - Three.js材质对象
     * @returns {Object} 材质属性对象
     */
    extractMaterialProperties(material) {
        const properties = {
            type: material.type, // 材质类型（源材质类型）
            name: material.name || 'Unnamed Material'
        };
        
        // 基础颜色
        if (material.color) {
            properties.color = `#${material.color.getHexString()}`;
            
            // 🎯 关键修复：检查并修正黑色透明材质
            const isTransparent = material.transparent || material.alphaTest > 0 || material.opacity < 1;
            if (properties.color === '#000000' && isTransparent) {
                console.log(`🎨 [颜色修正] 发现黑色透明材质，自动修正为白色: ${material.name}`);
                properties.color = '#ffffff';
            }
        }
        
        // PBR 属性（MeshStandardMaterial, MeshPhysicalMaterial）
        if (material.metalness !== undefined) {
            properties.metalness = material.metalness;
        }
        if (material.roughness !== undefined) {
            properties.roughness = material.roughness;
        }
        
        // 🎯 高光属性转换（MeshPhongMaterial -> MeshPhysicalMaterial）
        // 如果是Phong材质且没有PBR参数，从高光参数计算PBR参数
        if (material.type === 'MeshPhongMaterial') {
            // shininess转换为roughness（shininess越高，表面越光滑，roughness越低）
            // Phong的shininess通常在0-100范围，默认30
            if (material.shininess !== undefined && properties.roughness === undefined) {
                // 转换公式：roughness = 1.0 - sqrt(shininess / 100)
                const normalizedShininess = Math.min(material.shininess / 100, 1.0);
                properties.roughness = 1.0 - Math.sqrt(normalizedShininess);
                properties._convertedFromShininess = material.shininess; // 记录原始值
            }
            
            // specular颜色转换：如果specular较亮，说明反射性强，可以提高metalness
            if (material.specular && properties.metalness === undefined) {
                // 计算specular的亮度（0-1）
                const r = material.specular.r;
                const g = material.specular.g;
                const b = material.specular.b;
                const specularBrightness = (r + g + b) / 3.0;
                
                // 如果高光较亮，适度提高金属度（但不要过高）
                if (specularBrightness > 0.3) {
                    properties.metalness = Math.min(specularBrightness * 0.5, 0.8);
                    properties._convertedFromSpecular = `#${material.specular.getHexString()}`;
                } else {
                    properties.metalness = 0.0; // 低高光 = 非金属
                }
            }
        }
        
        // 发光属性
        if (material.emissive) {
            properties.emissive = `#${material.emissive.getHexString()}`;
        }
        if (material.emissiveIntensity !== undefined) {
            properties.emissiveIntensity = material.emissiveIntensity;
        }
        
        // 透明度
        if (material.opacity !== undefined) {
            properties.opacity = material.opacity;
        }
        if (material.transparent !== undefined) {
            properties.transparent = material.transparent;
        }
        
        // 双面渲染
        if (material.side !== undefined) {
            properties.side = material.side; // THREE.FrontSide, THREE.BackSide, THREE.DoubleSide
        }
        
        // AO 强度
        if (material.aoMapIntensity !== undefined) {
            properties.aoMapIntensity = material.aoMapIntensity;
        }
        
        // 凹凸强度
        if (material.bumpScale !== undefined) {
            properties.bumpScale = material.bumpScale;
        }
        
        // 法线贴图强度
        if (material.normalScale) {
            properties.normalScale = {
                x: material.normalScale.x,
                y: material.normalScale.y
            };
        }
        
        // 其他常用属性
        if (material.flatShading !== undefined) {
            properties.flatShading = material.flatShading;
        }
        if (material.wireframe !== undefined) {
            properties.wireframe = material.wireframe;
        }
        if (material.vertexColors !== undefined) {
            properties.vertexColors = material.vertexColors;
        }
        
        return properties;
    }
    
    /**
     * 从材质中提取贴图信息
     * @param {THREE.Material} material - Three.js材质对象
     * @returns {Object|null} 贴图信息对象
     */
    extractTexturesFromMaterial(material) {
        const textures = {};
        let hasTexture = false;
        
        // 定义需要提取的贴图类型（扩展以支持FBX）
        const textureTypes = [
            'map',              // 基础颜色贴图 (Diffuse)
            'normalMap',        // 法线贴图
            'bumpMap',          // 凹凸贴图
            'aoMap',            // 环境光遮蔽贴图
            'roughnessMap',     // 粗糙度贴图
            'metalnessMap',     // 金属度贴图
            'emissiveMap',      // 自发光贴图
            'specularMap',      // 镜面反射贴图
            'displacementMap',  // 置换贴图
            'alphaMap',         // 透明度贴图
            'lightMap',         // 光照贴图
            'envMap'            // 环境贴图
        ];
        
        for (const textureType of textureTypes) {
            const texture = material[textureType];
            
            // 🔍 调试：检查贴图对象
            if (texture) {
                console.log(`  📌 ${textureType}:`, {
                    exists: true,
                    hasImage: !!texture.image,
                    imageType: texture.image ? texture.image.constructor.name : 'N/A',
                    imageSrc: texture.image?.src ? texture.image.src.substring(0, 80) + '...' : 'N/A',
                    imageComplete: texture.image?.complete,
                    imageWidth: texture.image?.width || texture.image?.naturalWidth,
                    imageHeight: texture.image?.height || texture.image?.naturalHeight
                });
            }
            
            if (texture && texture.image) {
                hasTexture = true;
                
                // 提取贴图URL（优先使用image.src，否则生成DataURL）
                let textureUrl = null;
                if (texture.image.src && !texture.image.src.startsWith('blob:')) {
                    // 如果有有效的src URL（非blob）
                    textureUrl = texture.image.src;
                } else if (texture.image.complete) {
                    // 否则尝试生成DataURL
                    try {
                        const canvas = document.createElement('canvas');
                        canvas.width = texture.image.width || texture.image.naturalWidth || 256;
                        canvas.height = texture.image.height || texture.image.naturalHeight || 256;
                        const ctx = canvas.getContext('2d');
                        ctx.drawImage(texture.image, 0, 0);
                        textureUrl = canvas.toDataURL('image/jpeg', 0.8);
                    } catch (e) {
                        console.warn(`⚠️ 无法生成贴图DataURL: ${textureType}`, e);
                    }
                }
                
                // 提取贴图数据
                textures[textureType] = {
                    image: texture.image,  // ✅ 保留image对象用于运行时预览
                    texture: texture,      // ✅ 保留完整的Three.js Texture对象
                    url: textureUrl,       // ✅ 添加URL字段
                    name: texture.name || `${material.name}_${textureType}`,
                    // UV变换信息
                    repeat: { x: texture.repeat.x, y: texture.repeat.y },
                    offset: { x: texture.offset.x, y: texture.offset.y },
                    rotation: texture.rotation,
                    center: { x: texture.center.x, y: texture.center.y }
                };
                
                console.log(`  ✅ 提取成功 ${textureType}: ${textureUrl ? '有URL' : '无URL'}`);
            }
        }
        
        // 🔍 检查userData中是否有贴图信息（FBX特殊情况）
        if (material.userData && Object.keys(material.userData).length > 0) {
            console.log(`  📦 材质userData:`, material.userData);
        }
        
        // 🔍 检查是否有任何以"map"结尾的属性
        const allMapProps = Object.keys(material).filter(k => 
            (k.toLowerCase().includes('map') || k.toLowerCase().includes('texture')) && 
            material[k] && 
            typeof material[k] === 'object'
        );
        if (allMapProps.length > 0) {
            console.log(`  🔎 发现潜在贴图属性:`, allMapProps);
            allMapProps.forEach(prop => {
                if (!textureTypes.includes(prop) && material[prop].image) {
                    console.log(`  ⚡ 未识别的贴图类型: ${prop}`, material[prop]);
                }
            });
        }
        
        return hasTexture ? textures : null;
    }

    /**
     * 更新缓存中的模型mesh材质信息
     */
    async updateModelMeshMaterials(modelId, meshMaterials) {
        try {
            // ⭐ 兼容两种格式：
            // 1. Cache阶段：meshData 是完整对象 { materialName, textures, properties }
            // 2. 最终保存：meshData 是简单字符串（materialName）
            const serializedMeshMaterials = {};
            for (const [meshName, meshData] of Object.entries(meshMaterials)) {
                if (typeof meshData === 'string') {
                    // ✅ 简化格式：直接赋值
                    serializedMeshMaterials[meshName] = meshData;
                } else {
                    // ✅ 完整格式：清理不可序列化的对象
                    serializedMeshMaterials[meshName] = {
                        ...meshData,
                        textures: meshData.textures ? this.serializeTextures(meshData.textures) : null
                    };
                }
            }
            
            const response = await fetch(`/api/cache/${modelId}/meshMaterials`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    projectId: this.projectId,
                    meshMaterials: serializedMeshMaterials 
                })
            });

            const result = await response.json();
            if (!result.success) {
                throw new Error(result.error || '更新mesh材质信息失败');
            }
        } catch (error) {
            console.error('❌ 更新mesh材质信息失败:', error);
            throw error;
        }
    }
    
    /**
     * 序列化贴图信息，移除不可序列化的对象
     * @param {Object} textures - 贴图信息对象
     * @returns {Object} 可序列化的贴图信息
     */
    serializeTextures(textures) {
        const serialized = {};
        for (const [textureType, textureData] of Object.entries(textures)) {
            // ✅ 只保留基本元数据，不保存巨大的DataURL
            const url = textureData.url;
            let urlToSave = null;
            
            // 如果是DataURL或blob URL，不保存（太大），只保存标记
            if (url) {
                if (url.startsWith('data:')) {
                    urlToSave = '[embedded-texture]';  // 标记为嵌入贴图
                } else if (url.startsWith('blob:')) {
                    urlToSave = '[blob-texture]';  // 标记为blob贴图
                } else {
                    urlToSave = url;  // 保存实际URL
                }
            }
            
            serialized[textureType] = {
                url: urlToSave,
                name: textureData.name,
                hasTexture: !!url,  // ✅ 标记是否有贴图
                repeat: textureData.repeat,
                offset: textureData.offset,
                rotation: textureData.rotation,
                center: textureData.center
                // ❌ 不保存 image 对象、texture对象和巨大的DataURL
            };
            
            console.log(`  💾 序列化贴图 ${textureType}: ${urlToSave}`);
        }
        return serialized;
    }
    
    /**
     * 上传贴图到贴图库
     * @param {HTMLImageElement|HTMLCanvasElement} image - 图片对象
     * @param {string} textureName - 贴图名称
     * @returns {Promise<string>} 返回贴图ID
     */
    async uploadTextureToLibrary(image, textureName) {
        try {
            console.log(`📤 [上传贴图] 开始上传: ${textureName}`);
            
            // 将图片转换为Blob
            const blob = await this.imageToBlob(image);
            
            // 创建FormData
            const formData = new FormData();
            // 根据图片格式生成文件名
            const ext = this.getImageExtension(image);
            const fileName = `${textureName}${ext}`;
            formData.append('texture', blob, fileName);
            formData.append('originalFileName', fileName);
            
            // 上传到贴图库
            const response = await fetch(`/api/projects/${this.projectId}/textures/upload`, {
                method: 'POST',
                body: formData
            });
            
            const result = await response.json();
            
            if (result.success && result.data) {
                console.log(`✅ [上传贴图] 上传成功: ${textureName} -> ${result.data.id}`);
                return result.data.id;
            } else {
                throw new Error(result.error || '上传贴图失败');
            }
        } catch (error) {
            console.error(`❌ [上传贴图] 上传失败: ${textureName}`, error);
            return null;
        }
    }
    
    /**
     * 将图片转换为Blob
     * @param {HTMLImageElement|HTMLCanvasElement} image
     * @returns {Promise<Blob>}
     */
    async imageToBlob(image) {
        return new Promise((resolve, reject) => {
            // 如果已经是Blob，直接返回
            if (image instanceof Blob) {
                resolve(image);
                return;
            }
            
            // 创建canvas
            const canvas = document.createElement('canvas');
            canvas.width = image.width || image.naturalWidth;
            canvas.height = image.height || image.naturalHeight;
            
            const ctx = canvas.getContext('2d');
            ctx.drawImage(image, 0, 0);
            
            // 转换为Blob
            canvas.toBlob((blob) => {
                if (blob) {
                    resolve(blob);
                } else {
                    reject(new Error('图片转换失败'));
                }
            }, 'image/png');
        });
    }
    
    /**
     * 获取图片扩展名
     * @param {HTMLImageElement|HTMLCanvasElement} image
     * @returns {string}
     */
    getImageExtension(image) {
        // 如果图片有src属性，尝试从src中获取扩展名
        if (image.src) {
            const match = image.src.match(/\.(\w+)(?:\?|$)/);
            if (match) {
                return `.${match[1].toLowerCase()}`;
            }
        }
        // 默认使用png
        return '.png';
    }

    /**
     * 解析材质信息（在阶段一执行）
     */
    async analyzeMaterials() {
        const btnAnalyze = document.getElementById('btn-analyze-materials');
        
        try {
            // 禁用解析按钮并更新文本
            btnAnalyze.disabled = true;
            const originalText = btnAnalyze.innerHTML;
            btnAnalyze.innerHTML = '🔍 解析中...';
            
            // 加载材质库
            await this.materialLibraryManager.loadMaterials();
            
            // 从缓存加载最新的 models.json
            await this.loadCacheModels();
            
            // 🎯 分析所有模型的材质使用情况
            this.analyzeMaterialUsage();
            
            // 标记为已解析，没有未解析的模型
            this.materialsAnalyzed = true;
            this.hasUnanalyzedModels = false;
            
            // 恢复按钮文本
            btnAnalyze.innerHTML = originalText;
            
            // 显示审核信息和完成按钮
            this.showReviewSection();
            
        } catch (error) {
            console.error('❌ 解析材质失败:', error);
            alert('解析材质失败: ' + error.message);
            btnAnalyze.disabled = false;
            btnAnalyze.innerHTML = '🔍 解析材质';
        }
    }

    /**
     * 显示材质审核区域
     */
    showReviewSection() {
        const reviewSection = document.getElementById('material-review-section');
        const reviewSummary = document.getElementById('review-summary');
        const btnAnalyze = document.getElementById('btn-analyze-materials');
        const btnReview = document.getElementById('btn-open-material-review');
        const btnComplete = document.getElementById('btn-complete-upload');

        // 统计材质信息
        let totalMaterials = this.materialUsageMap.size;
        let matchedMaterials = 0;
        let unmatchedMaterials = 0;

        for (const [materialName, info] of this.materialUsageMap) {
            if (info.matchingMaterial) {
                matchedMaterials++;
            } else {
                unmatchedMaterials++;
            }
        }

        // 显示审核摘要
        reviewSummary.innerHTML = `
            <div class="summary-title">📊 材质解析结果</div>
            <div class="summary-items">
                <div class="summary-item">
                    <span class="summary-label">检测到材质：</span>
                    <span class="summary-value">${totalMaterials} 个</span>
                </div>
                <div class="summary-item">
                    <span class="summary-label">材质库有同名：</span>
                    <span class="summary-value success">${matchedMaterials} 个</span>
                </div>
                <div class="summary-item">
                    <span class="summary-label">新建材质：</span>
                    <span class="summary-value warning">${unmatchedMaterials} 个</span>
                </div>
            </div>
        `;

        // 显示审核摘要区域
        reviewSection.style.display = 'block';
        
        // 禁用解析按钮
        btnAnalyze.disabled = true;
        
        // 显示材质信息按钮和完成按钮
        btnReview.style.display = 'inline-block';
        btnComplete.style.display = 'inline-block';
    }

    /**
     * 打开材质审核对话框（独立窗口）
     */
    openMaterialReviewDialog() {
        if (!this.materialsAnalyzed) {
            alert('请先解析材质信息');
            return;
        }

        // 显示审核对话框
        this.showMaterialReviewDialog();
    }
    
    /**
     * 分析所有上传模型的材质使用情况
     */
    analyzeMaterialUsage() {
        this.materialUsageMap = new Map(); // 材质名 -> 使用信息列表
        
        console.log('🔍 [解析材质] 开始分析材质使用情况...');
        
        for (const model of this.cacheModels) {
            console.log(`📦 [解析材质] 模型: ${model.displayName}, Mesh数量: ${Object.keys(model.meshMaterials || {}).length}`);
            
            for (const [meshName, meshData] of Object.entries(model.meshMaterials || {})) {
                // ✅ 新架构：从 materialName 字段获取材质名
                const materialName = fixStringEncoding(meshData.materialName);
                
                // 在材质库中查找同名材质
                const matchingMaterial = this.materialLibraryManager.materials.find(
                    mat => mat.name === materialName || mat.displayName === materialName
                );
                
                // 如果该材质名还没有记录，初始化
                if (!this.materialUsageMap.has(materialName)) {
                    this.materialUsageMap.set(materialName, {
                        materialName: materialName,
                        matchingMaterial: matchingMaterial,
                        usages: [] // 使用该材质的模型和mesh列表
                    });
                    
                    console.log(`    ✨ [新材质] "${materialName}" ${matchingMaterial ? '(已有同名)' : '(新建)'}`);
                }
                
                // 添加使用信息
                this.materialUsageMap.get(materialName).usages.push({
                    modelId: model.id,
                    modelName: model.displayName,
                    meshId: meshName, // ✅ 现在meshId就是meshName（同名mesh已添加索引）
                    meshName: meshName
                });
            }
        }
        
        console.log(`✅ [解析材质] 完成，共检测到 ${this.materialUsageMap.size} 个不同的材质`);
    }

    /**
     * 从缓存加载模型数据
     */
    async loadCacheModels() {
        try {
            const response = await fetch(`/api/cache?projectId=${this.projectId}`);
            const result = await response.json();
            
            if (result.success) {
                // ✅ 合并数据：保留本地已有模型的 texturesMap
                const serverModels = result.data;
                const mergedModels = [];
                
                for (const serverModel of serverModels) {
                    // 查找本地是否已有此模型
                    const localModel = this.cacheModels.find(m => m.id === serverModel.id);
                    
                    if (localModel && localModel.texturesMap) {
                        // ✅ 合并：使用服务器数据，但保留本地的 texturesMap
                        console.log(`🔄 [加载缓存] 合并模型 ${serverModel.id}，保留texturesMap (${localModel.texturesMap.size || Object.keys(localModel.texturesMap).length} 个材质)`);
                        mergedModels.push({
                            ...serverModel,
                            texturesMap: localModel.texturesMap  // ✅ 保留本地的 texturesMap
                        });
                    } else {
                        // 新模型，直接使用服务器数据
                        mergedModels.push(serverModel);
                    }
                }
                
                this.cacheModels = mergedModels;
                console.log(`✅ [加载缓存] 加载完成，共 ${mergedModels.length} 个模型`);
            } else {
                throw new Error(result.error || '加载缓存模型失败');
            }
        } catch (error) {
            console.error('❌ 加载缓存模型失败:', error);
            throw error;
        }
    }

    /**
     * 显示材质审核对话框（独立窗口，不关闭阶段一）
     */
    showMaterialReviewDialog() {
        // 如果已经有审核对话框打开，先关闭
        if (this.reviewDialogElement) {
            this.reviewDialogElement.remove();
        }

        const dialog = document.createElement('div');
        dialog.className = 'two-stage-upload-dialog material-review-dialog';
        dialog.innerHTML = `
            <div class="two-stage-upload-overlay"></div>
            <div class="two-stage-upload-content stage-two">
                <div class="two-stage-upload-header">
                    <div class="header-left">
                        <h2>🎨 材质信息</h2>
                    </div>
                    <div class="header-right">
                        <button class="close-btn" id="btn-close-review-dialog">✕</button>
                    </div>
                </div>
                
                <!-- Tab 切换栏 -->
                <div class="material-info-tabs">
                    <button class="material-tab-btn active" data-tab="materials">
                        🎨 材质
                    </button>
                    <button class="material-tab-btn" data-tab="textures">
                        🖼️ 贴图
                    </button>
                </div>
                
                <div class="two-stage-upload-body">
                    <!-- 材质Tab内容 -->
                    <div class="material-tab-content active" id="tab-materials">
                        <div class="material-match-list" id="material-match-list"></div>
                    </div>
                    
                    <!-- 贴图Tab内容 -->
                    <div class="material-tab-content" id="tab-textures">
                        <div class="texture-info-container" id="texture-info-container">
                            <div class="loading-textures">正在加载贴图信息...</div>
                        </div>
                    </div>
                </div>
                
                <div class="two-stage-upload-footer">
                    <div class="footer-hint">💡 提示：关闭此窗口后，可在主窗口点击"完成上传"按钮应用材质配置</div>
                </div>
            </div>
            
            <style>
                /* Tab 切换栏样式 */
                .material-info-tabs {
                    display: flex;
                    gap: 0;
                    background: #1a1a1a;
                    border-bottom: 2px solid #333;
                    padding: 0 20px;
                }
                
                .material-tab-btn {
                    flex: 1;
                    padding: 14px 20px;
                    background: transparent;
                    border: none;
                    color: #999;
                    font-size: 15px;
                    font-weight: 600;
                    cursor: pointer;
                    transition: all 0.3s;
                    position: relative;
                    border-bottom: 3px solid transparent;
                }
                
                .material-tab-btn:hover {
                    color: #6ec1ff;
                    background: rgba(102, 126, 234, 0.05);
                }
                
                .material-tab-btn.active {
                    color: #6ec1ff;
                    background: rgba(102, 126, 234, 0.1);
                    border-bottom-color: #667eea;
                }
                
                .material-tab-btn.active::after {
                    content: '';
                    position: absolute;
                    bottom: -2px;
                    left: 0;
                    right: 0;
                    height: 3px;
                    background: linear-gradient(90deg, #667eea, #764ba2);
                    box-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
                }
                
                /* Tab 内容区域 */
                .material-tab-content {
                    display: none;
                }
                
                .material-tab-content.active {
                    display: block;
                }
                
                /* 贴图信息容器 */
                .texture-info-container {
                    padding: 20px;
                }
                
                .loading-textures {
                    text-align: center;
                    color: #999;
                    padding: 60px 20px;
                    font-size: 14px;
                }
                
                .texture-category {
                    margin-bottom: 30px;
                }
                
                .texture-category-title {
                    font-size: 16px;
                    font-weight: 600;
                    color: #6ec1ff;
                    margin: 0 0 16px 0;
                    padding: 12px 16px;
                    background: rgba(102, 126, 234, 0.1);
                    border-left: 4px solid #667eea;
                    border-radius: 4px;
                }
                
                .texture-grid {
                    display: grid;
                    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
                    gap: 16px;
                }
                
                .texture-card {
                    background: rgba(30, 30, 35, 0.8);
                    border: 1px solid #444;
                    border-radius: 8px;
                    padding: 14px;
                    transition: all 0.2s;
                }
                
                .texture-card:hover {
                    background: rgba(40, 40, 45, 0.9);
                    border-color: #667eea;
                    transform: translateY(-3px);
                    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.25);
                }
                
                .texture-card.existing {
                    border-left: 4px solid #4caf50;
                }
                
                .texture-card.new {
                    border-left: 4px solid #ff9800;
                }
                
                .texture-preview {
                    width: 100%;
                    height: 140px;
                    background: #0a0a0a;
                    border-radius: 6px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    margin-bottom: 10px;
                    overflow: hidden;
                    border: 1px solid #333;
                }
                
                .texture-preview img {
                    max-width: 100%;
                    max-height: 100%;
                    object-fit: contain;
                }
                
                .texture-preview .no-preview {
                    color: #666;
                    font-size: 13px;
                }
                
                .texture-name {
                    font-size: 14px;
                    color: #fff;
                    font-weight: 500;
                    margin-bottom: 6px;
                    word-break: break-all;
                    line-height: 1.4;
                }
                
                .texture-type {
                    font-size: 12px;
                    color: #999;
                    margin-bottom: 8px;
                }
                
                .texture-status {
                    font-size: 11px;
                    padding: 5px 10px;
                    border-radius: 4px;
                    display: inline-block;
                    font-weight: 600;
                }
                
                .texture-status.existing {
                    background: rgba(76, 175, 80, 0.2);
                    color: #81c784;
                }
                
                .texture-status.new {
                    background: rgba(255, 152, 0, 0.2);
                    color: #ffb74d;
                }
                
                .no-textures {
                    text-align: center;
                    color: #666;
                    padding: 60px 20px;
                    font-size: 14px;
                }
            </style>
        `;
        
        document.body.appendChild(dialog);
        this.reviewDialogElement = dialog;
        
        // 🎯 重要：先渲染材质匹配列表（创建DOM元素），再绑定事件和初始化状态
        // 渲染材质匹配列表
        this.renderMaterialMatchList();
        
        // 绑定事件（内部会调用handleGlobalToggle来设置默认状态）
        this.bindReviewDialogEvents();
    }

    /**
     * 绑定审核对话框的事件
     */
    bindReviewDialogEvents() {
        const btnClose = document.getElementById('btn-close-review-dialog');

        // 关闭按钮 - 只关闭审核窗口，不清空缓存
        btnClose.addEventListener('click', () => {
            this.closeReviewDialog();
        });
        
        // 绑定Tab切换事件
        const tabBtns = this.reviewDialogElement.querySelectorAll('.material-tab-btn');
        const tabContents = this.reviewDialogElement.querySelectorAll('.material-tab-content');
        
        tabBtns.forEach(btn => {
            btn.addEventListener('click', () => {
                const targetTab = btn.getAttribute('data-tab');
                
                // 更新按钮状态
                tabBtns.forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
                
                // 更新内容显示
                tabContents.forEach(content => {
                    if (content.id === `tab-${targetTab}`) {
                        content.classList.add('active');
                        
                        // 如果切换到贴图tab且还未加载，则加载贴图信息
                        if (targetTab === 'textures' && !content.dataset.loaded) {
                            content.dataset.loaded = 'true';
                            this.loadAllTexturesInfo();
                        }
                    } else {
                        content.classList.remove('active');
                    }
                });
            });
        });
    }

    /**
     * 关闭审核对话框
     */
    closeReviewDialog() {
        if (this.reviewDialogElement) {
            this.reviewDialogElement.remove();
            this.reviewDialogElement = null;
        }
    }
    
    /**
     * 加载所有贴图信息
     */
    async loadAllTexturesInfo() {
        const container = document.getElementById('texture-info-container');
        container.innerHTML = '<div class="loading-textures">正在加载贴图信息...</div>';
        
        try {
            // 1. 获取项目贴图库的所有贴图
            const response = await fetch(`/api/projects/${this.projectId}/textures`);
            const result = await response.json();
            
            if (!result.success) {
                throw new Error(result.error || '加载贴图库失败');
            }
            
            const libraryTextures = result.data || [];
            
            console.log('📚 [贴图信息] 贴图库数据:', libraryTextures);
            
            // 2. 从所有模型中收集贴图信息
            const allTextures = this.collectAllTextures();
            
            console.log('📝 [贴图信息] 收集的贴图:', allTextures);
            
            if (allTextures.length === 0) {
                container.innerHTML = '<div class="no-textures">没有检测到任何贴图</div>';
                return;
            }
            
            // 3. 分类：同名贴图 vs 新贴图
            const existingTextures = [];
            const newTextures = [];
            
            for (const textureInfo of allTextures) {
                // 检查贴图库中是否存在同名贴图（基于originalFileName匹配）
                const matchingTexture = libraryTextures.find(libTex => {
                    const libName = libTex.originalFileName || libTex.fileName || libTex.name;
                    return libName === textureInfo.name;
                });
                
                if (matchingTexture) {
                    existingTextures.push({
                        ...textureInfo,
                        matchingTexture
                    });
                } else {
                    newTextures.push(textureInfo);
                }
            }
            
            console.log('✅ [贴图信息] 同名贴图:', existingTextures);
            console.log('📦 [贴图信息] 新贴图:', newTextures);
            
            // 4. 生成HTML
            const html = this.generateAllTexturesHTML(existingTextures, newTextures);
            container.innerHTML = html;
            
        } catch (error) {
            console.error('❌ [贴图信息] 加载失败:', error);
            container.innerHTML = `<div class="no-textures">加载贴图信息失败: ${error.message}</div>`;
        }
    }
    
    /**
     * 从所有上传的模型中收集贴图信息
     * @returns {Array} 贴图信息数组
     */
    collectAllTextures() {
        const textureMap = new Map(); // 使用Map去重（基于贴图名称）
        
        for (const model of this.cacheModels) {
            console.log(`🔍 [收集贴图] 模型: ${model.displayName}`, {
                hasTexturesMap: !!model.texturesMap,
                texturesMapType: model.texturesMap?.constructor?.name
            });
            
            // 从texturesMap中获取贴图（这里保存了完整的texture对象）
            if (model.texturesMap) {
                let texturesData = null;
                
                // 处理Map类型
                if (model.texturesMap instanceof Map) {
                    texturesData = Array.from(model.texturesMap.values());
                } 
                // 处理普通对象
                else if (typeof model.texturesMap === 'object') {
                    texturesData = Object.values(model.texturesMap);
                }
                
                if (texturesData) {
                    for (const materialTextures of texturesData) {
                        // materialTextures 是一个材质的所有贴图
                        if (materialTextures && typeof materialTextures === 'object') {
                            this.extractTexturesFromMaterialData(materialTextures, textureMap);
                        }
                    }
                }
            }
        }
        
        console.log(`📊 [收集贴图] 总计: ${textureMap.size} 个唯一贴图`);
        
        return Array.from(textureMap.values());
    }
    
    /**
     * 从材质贴图数据中提取贴图信息
     * @param {Object} materialTextures - 材质的贴图数据
     * @param {Map} textureMap - 贴图Map（用于去重）
     */
    extractTexturesFromMaterialData(materialTextures, textureMap) {
        const textureTypes = {
            map: '基础颜色贴图',
            normalMap: '法线贴图',
            bumpMap: '凹凸贴图',
            aoMap: 'AO贴图',
            roughnessMap: '粗糙度贴图',
            metalnessMap: '金属度贴图',
            emissiveMap: '自发光贴图',
            alphaMap: '透明度贴图',
            displacementMap: '置换贴图',
            specularMap: '镜面反射贴图',
            lightMap: '光照贴图'
        };
        
        for (const [key, label] of Object.entries(textureTypes)) {
            const textureData = materialTextures[key];
            if (textureData) {
                // 提取贴图名称
                let textureName = '';
                
                if (textureData.name) {
                    textureName = textureData.name;
                } else if (textureData.texture && textureData.texture.name) {
                    textureName = textureData.texture.name;
                } else if (textureData.url) {
                    const urlParts = textureData.url.split('/');
                    textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                    textureName = textureName.split('?')[0];
                } else if (textureData.image && textureData.image.src) {
                    const urlParts = textureData.image.src.split('/');
                    textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                    textureName = textureName.split('?')[0];
                }
                
                if (!textureName) {
                    textureName = `未命名_${key}`;
                }
                
                // 使用贴图名称作为key去重
                if (!textureMap.has(textureName)) {
                    textureMap.set(textureName, {
                        name: textureName,
                        type: key,
                        typeLabel: label,
                        textureData: textureData
                    });
                }
            }
        }
    }
    
    /**
     * 生成所有贴图信息的HTML
     * @param {Array} existingTextures - 同名贴图列表
     * @param {Array} newTextures - 新贴图列表
     * @returns {string} HTML字符串
     */
    generateAllTexturesHTML(existingTextures, newTextures) {
        let html = '';
        
        // 同名贴图（可复用）
        if (existingTextures.length > 0) {
            html += `
                <div class="texture-category">
                    <h3 class="texture-category-title">✅ 贴图库有同名贴图 (${existingTextures.length})</h3>
                    <div class="texture-grid">
            `;
            
            for (const tex of existingTextures) {
                html += this.generateTextureCardHTML(tex, true);
            }
            
            html += `
                    </div>
                </div>
            `;
        }
        
        // 新贴图
        if (newTextures.length > 0) {
            html += `
                <div class="texture-category">
                    <h3 class="texture-category-title">📦 新建贴图 (${newTextures.length})</h3>
                    <div class="texture-grid">
            `;
            
            for (const tex of newTextures) {
                html += this.generateTextureCardHTML(tex, false);
            }
            
            html += `
                    </div>
                </div>
            `;
        }
        
        return html;
    }
    
    /**
     * 生成单个贴图卡片HTML
     * @param {Object} textureInfo - 贴图信息
     * @param {boolean} isExisting - 是否为已存在的贴图
     * @returns {string} HTML字符串
     */
    generateTextureCardHTML(textureInfo, isExisting) {
        const statusClass = isExisting ? 'existing' : 'new';
        const statusText = isExisting ? '已存在（复用）' : '新建贴图';
        
        // 尝试获取贴图预览
        let previewHTML = '<div class="no-preview">无预览</div>';
        
        if (textureInfo.textureData) {
            const data = textureInfo.textureData;
            
            // 1. 优先使用url字段
            if (data.url) {
                previewHTML = `<img src="${data.url}" alt="${textureInfo.name}">`;
            }
            // 2. 使用image对象
            else if (data.image) {
                const img = data.image;
                if (img.currentSrc || img.src) {
                    previewHTML = `<img src="${img.currentSrc || img.src}" alt="${textureInfo.name}">`;
                } else if (img instanceof HTMLCanvasElement) {
                    try {
                        const dataUrl = img.toDataURL();
                        previewHTML = `<img src="${dataUrl}" alt="${textureInfo.name}">`;
                    } catch (e) {
                        console.warn('无法转换Canvas为预览图:', e);
                    }
                }
            }
            // 3. 从texture对象获取
            else if (data.texture && data.texture.image) {
                const img = data.texture.image;
                if (img.currentSrc || img.src) {
                    previewHTML = `<img src="${img.currentSrc || img.src}" alt="${textureInfo.name}">`;
                }
            }
        }
        
        // 如果有匹配的贴图库贴图，使用贴图库的预览
        if (isExisting && textureInfo.matchingTexture) {
            const libTex = textureInfo.matchingTexture;
            const texturePath = `/api/projects/${this.projectId}/textures/${libTex.id}/file`;
            previewHTML = `<img src="${texturePath}" alt="${textureInfo.name}">`;
        }
        
        return `
            <div class="texture-card ${statusClass}">
                <div class="texture-preview">
                    ${previewHTML}
                </div>
                <div class="texture-name">${textureInfo.name || '未命名'}</div>
                <div class="texture-type">${textureInfo.typeLabel}</div>
                <div class="texture-status ${statusClass}">${statusText}</div>
            </div>
        `;
    }

    /**
     * 渲染材质匹配列表（按材质分组）
     */
    renderMaterialMatchList() {
        const listContainer = document.getElementById('material-match-list');
        listContainer.innerHTML = '';

        // 如果没有任何材质，显示提示
        if (this.materialUsageMap.size === 0) {
            listContainer.innerHTML = '<div class="no-matches">没有检测到任何材质。</div>';
            return;
        }

        // 分类材质：有同名材质的和无同名材质的
        const materialsWithMatch = [];
        const materialsWithoutMatch = [];
        
        for (const [materialName, info] of this.materialUsageMap) {
            if (info.matchingMaterial) {
                materialsWithMatch.push(info);
            } else {
                materialsWithoutMatch.push(info);
            }
        }

        // 渲染有同名材质的（优先显示）
        if (materialsWithMatch.length > 0) {
            const matchSection = document.createElement('div');
            matchSection.className = 'materials-category';
            matchSection.innerHTML = `
                <h3 class="category-title">✅ 材质库有同名材质 (${materialsWithMatch.length})</h3>
                <div class="category-content"></div>
            `;
            
            const matchContent = matchSection.querySelector('.category-content');
            
            for (const materialInfo of materialsWithMatch) {
                this.renderMaterialItem(matchContent, materialInfo, true);
            }
            
            listContainer.appendChild(matchSection);
        }

        // 渲染无同名材质的（新建材质）
        if (materialsWithoutMatch.length > 0) {
            const noMatchSection = document.createElement('div');
            noMatchSection.className = 'materials-category';
            noMatchSection.innerHTML = `
                <h3 class="category-title">📦 新建材质 (${materialsWithoutMatch.length})</h3>
                <div class="category-content"></div>
            `;
            
            const noMatchContent = noMatchSection.querySelector('.category-content');
            
            for (const materialInfo of materialsWithoutMatch) {
                this.renderMaterialItem(noMatchContent, materialInfo, false);
            }
            
            listContainer.appendChild(noMatchSection);
        }
    }

    /**
     * 渲染单个材质项（按材质分组）
     */
    renderMaterialItem(container, materialInfo, hasMatch) {
        const materialSection = document.createElement('div');
        materialSection.className = 'material-item';
        
        // 🎯 显示前再次修复编码
        const displayMaterialName = fixStringEncoding(materialInfo.materialName);
        materialSection.dataset.materialName = displayMaterialName;
        
        const usageCount = materialInfo.usages.length;
        const matchText = hasMatch 
            ? `→ 材质库: ${materialInfo.matchingMaterial.displayName || materialInfo.matchingMaterial.name}`
            : '→ 新建材质';
        
        materialSection.innerHTML = `
            <div class="material-header">
                <div class="material-info">
                    <span class="expand-icon">▶</span>
                    <span class="material-name">🎨 ${displayMaterialName}</span>
                    <span class="material-match-info">${matchText}</span>
                    <span class="material-stats">
                        ${usageCount} 个Mesh使用
                    </span>
                </div>
                <button class="btn-view-properties" title="查看材质详细参数">
                    🔍 查看参数
                </button>
            </div>
            <div class="material-content" style="display: none;">
                <div class="usage-list"></div>
            </div>
        `;
        
        const header = materialSection.querySelector('.material-header');
        const content = materialSection.querySelector('.material-content');
        const expandIcon = materialSection.querySelector('.expand-icon');
        const usageList = materialSection.querySelector('.usage-list');
        const btnViewProperties = materialSection.querySelector('.btn-view-properties');
        
        // 查看材质详细参数按钮
        btnViewProperties.addEventListener('click', (e) => {
            e.stopPropagation(); // 阻止事件冒泡，避免触发展开/折叠
            this.showMaterialPropertiesDialog(materialInfo);
        });
        
        // 展开/折叠功能
        header.addEventListener('click', (e) => {
            // 如果点击的是按钮，不触发展开/折叠
            if (e.target.closest('.btn-view-properties')) {
                return;
            }
            const isExpanded = content.style.display !== 'none';
            content.style.display = isExpanded ? 'none' : 'block';
            expandIcon.textContent = isExpanded ? '▶' : '▼';
            materialSection.classList.toggle('expanded', !isExpanded);
        });
        
        // 渲染使用该材质的所有mesh（按模型分组）
        const modelGroups = new Map();
        for (const usage of materialInfo.usages) {
            if (!modelGroups.has(usage.modelId)) {
                modelGroups.set(usage.modelId, {
                    modelId: usage.modelId,
                    modelName: usage.modelName,
                    meshes: []
                });
            }
            modelGroups.get(usage.modelId).meshes.push(usage);
        }
        
        for (const [modelId, group] of modelGroups) {
            const modelGroup = document.createElement('div');
            modelGroup.className = 'usage-model-group';
            // 🎯 修复模型名称编码
            const displayModelName = fixStringEncoding(group.modelName);
            modelGroup.innerHTML = `
                <div class="usage-model-name">📦 ${displayModelName} (${group.meshes.length} 个Mesh)</div>
                <div class="usage-mesh-list"></div>
            `;
            
            const meshList = modelGroup.querySelector('.usage-mesh-list');
            
            for (const usage of group.meshes) {
                const meshItem = document.createElement('div');
                meshItem.className = 'usage-mesh-item';
                // 🎯 修复mesh名称编码
                const displayMeshName = fixStringEncoding(usage.meshName);
                meshItem.innerHTML = `
                    <span class="mesh-name">🔹 ${displayMeshName}</span>
                `;
                meshList.appendChild(meshItem);
            }
            
            usageList.appendChild(modelGroup);
        }
        
        container.appendChild(materialSection);
    }

    /**
     * 显示材质详细参数对话框（使用材质编辑面板）
     * @param {Object} materialInfo - 材质信息
     */
    showMaterialPropertiesDialog(materialInfo) {
        // 获取第一个使用该材质的mesh的properties
        const properties = this.getMaterialPropertiesFromUsage(materialInfo);
        
        // 如果没有properties，显示提示
        if (!properties || Object.keys(properties).length === 0) {
            alert('该材质没有可用的详细参数信息');
            return;
        }
        
        // 获取贴图信息
        const textureInfo = this.getMaterialTexturesFromUsage(materialInfo);
        
        // 使用材质编辑面板展示
        this.showMaterialInEditorPanel(materialInfo, properties, textureInfo);
    }
    
    /**
     * 在材质编辑面板中展示材质（只读预览）
     * @param {Object} materialInfo - 材质信息
     * @param {Object} properties - 材质属性
     * @param {Object} textureInfo - 贴图信息
     */
    async showMaterialInEditorPanel(materialInfo, properties, textureInfo) {
        // 创建只读的材质预览面板
        this.showReadOnlyMaterialPanel(materialInfo, properties, textureInfo);
    }
    
    /**
     * 显示只读的材质预览面板
     * 
     * 📌 窗口宽度设计说明：
     * - 避免使用全局的 .modal-content 类名（限制为 max-width: 1000px）
     * - 使用自定义类名（如 .material-preview-container）
     * - 通过 CSS 变量或内联样式设置所需的宽度
     * - 本窗口设置为：width: 95%, max-width: 1400px
     * 
     * @param {Object} materialInfo - 材质信息
     * @param {Object} properties - 材质属性
     * @param {Object} textureInfo - 贴图信息
     */
    showReadOnlyMaterialPanel(materialInfo, properties, textureInfo) {
        // 创建对话框
        const dialog = document.createElement('div');
        dialog.className = 'material-preview-dialog';
        dialog.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.8);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 10001;
            backdrop-filter: blur(4px);
        `;
        
        // 创建临时的 Three.js 材质用于3D预览
        const tempMaterial = this.createThreeMaterialFromProperties(properties, textureInfo);
        
        // 生成参数HTML
        const paramsHTML = this.generateMaterialParamsHTML(properties, textureInfo);
        
        dialog.innerHTML = `
            <div class="material-readonly-preview-dialog-content">
                <!-- 头部 -->
                <div class="mpd-header">
                    <div class="mpd-header-info">
                        <h2 class="mpd-title">🔍 材质预览</h2>
                        <div class="mpd-subtitle">
                            <span class="mpd-material-name">${fixStringEncoding(materialInfo.materialName)}</span>
                            <span class="mpd-arrow">→</span>
                            <span class="mpd-material-type">MeshPhysicalMaterial</span>
                        </div>
                    </div>
                    <div class="mpd-header-actions">
                        <button class="mpd-preview-btn" title="查看材质球预览">
                            <span class="preview-icon">🎨</span>
                            <span>材质球预览</span>
                        </button>
                        <button class="mpd-close-btn">✕</button>
                    </div>
                </div>
                
                <!-- 3D预览悬浮窗（默认隐藏） -->
                <div class="mpd-preview-float" style="display: none;">
                    <div class="mpd-preview-float-header">
                        <span>🎨 3D 材质预览</span>
                        <button class="mpd-preview-close">✕</button>
                    </div>
                    <div class="mpd-preview-float-body">
                        <div id="material-preview-canvas"></div>
                    </div>
                </div>
                
                <!-- Tab 切换栏 -->
                <div class="mpd-tabs">
                    <button class="mpd-tab-btn active" data-tab="material">
                        🎨 材质信息
                    </button>
                    <button class="mpd-tab-btn" data-tab="texture">
                        🖼️ 贴图信息
                    </button>
                </div>
                
                <!-- 主体内容：Tab内容区域 -->
                <div class="mpd-body">
                    <!-- 材质信息Tab -->
                    <div class="mpd-tab-content active" id="tab-material">
                        <div class="mpd-params-container">
                            ${paramsHTML}
                        </div>
                    </div>
                    
                    <!-- 贴图信息Tab -->
                    <div class="mpd-tab-content" id="tab-texture">
                        <div class="mpd-textures-container">
                            <div class="loading-textures">正在加载贴图信息...</div>
                        </div>
                    </div>
                </div>
                
                <!-- 底部提示 -->
                <div class="mpd-footer">
                    <span class="mpd-footer-icon">💡</span>
                    <span>这是从源FBX材质转换为物理材质的预览结果（只读）</span>
                </div>
            </div>
            
            <style>
                /* 📌 避免与material-editor.css中的全局样式冲突，使用独特的类名 */
                .material-readonly-preview-dialog-content {
                    background: linear-gradient(135deg, #2a2a2a 0%, #252525 100%);
                    border-radius: 12px;
                    width: 90%;
                    max-width: 500px;  /* ✅ 宽度缩小一半 */
                    height: 85vh;
                    display: flex !important;
                    flex-direction: column !important;
                    overflow: hidden;
                    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.7);
                    border: 1px solid rgba(102, 126, 234, 0.2);
                    position: relative;
                }
                
                .mpd-header {
                    padding: 16px 20px;
                    border-bottom: 1px solid #444;
                    background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    flex-shrink: 0;
                }
                
                /* Tab 切换栏样式 */
                .mpd-tabs {
                    display: flex;
                    gap: 0;
                    background: #1a1a1a;
                    border-bottom: 2px solid #333;
                    flex-shrink: 0;
                }
                
                .mpd-tab-btn {
                    flex: 1;
                    padding: 14px 20px;
                    background: transparent;
                    border: none;
                    color: #999;
                    font-size: 15px;
                    font-weight: 600;
                    cursor: pointer;
                    transition: all 0.3s;
                    position: relative;
                    border-bottom: 3px solid transparent;
                }
                
                .mpd-tab-btn:hover {
                    color: #6ec1ff;
                    background: rgba(102, 126, 234, 0.05);
                }
                
                .mpd-tab-btn.active {
                    color: #6ec1ff;
                    background: rgba(102, 126, 234, 0.1);
                    border-bottom-color: #667eea;
                }
                
                .mpd-tab-btn.active::after {
                    content: '';
                    position: absolute;
                    bottom: -2px;
                    left: 0;
                    right: 0;
                    height: 3px;
                    background: linear-gradient(90deg, #667eea, #764ba2);
                    box-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
                }
                
                /* Tab 内容区域 */
                .mpd-tab-content {
                    display: none;
                    width: 100%;
                    height: 100%;
                }
                
                .mpd-tab-content.active {
                    display: block;
                }
                
                .mpd-header-info {
                    flex: 1;
                    min-width: 0;
                }
                
                .mpd-header-actions {
                    display: flex;
                    align-items: center;
                    gap: 12px;
                }
                
                .mpd-title {
                    margin: 0 0 8px 0;
                    color: #fff;
                    font-size: 24px;  /* ✅ 18px × 1.5 ≈ 27px */
                    font-weight: 600;
                    letter-spacing: 0.5px;
                }
                
                .mpd-subtitle {
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    color: #aaa;
                    font-size: 17px;  /* ✅ 13px × 1.5 ≈ 19.5px */
                    flex-wrap: wrap;
                }
                
                .mpd-material-name {
                    color: #4a9eff;
                    font-weight: 500;
                }
                
                .mpd-arrow {
                    color: #666;
                }
                
                .mpd-material-type {
                    color: #6ec1ff;
                    background: rgba(74, 158, 255, 0.1);
                    padding: 4px 12px;
                    border-radius: 5px;
                    font-size: 15px;  /* ✅ 11px × 1.5 ≈ 16.5px */
                    font-weight: 500;
                }
                
                /* 材质球预览按钮 */
                .mpd-preview-btn {
                    background: linear-gradient(135deg, rgba(102, 126, 234, 0.25), rgba(118, 75, 162, 0.25));
                    border: 1px solid rgba(102, 126, 234, 0.5);
                    border-radius: 8px;
                    color: #6ec1ff;
                    font-size: 16px;  /* ✅ 12px × 1.5 ≈ 18px */
                    font-weight: 600;
                    padding: 8px 16px;
                    cursor: pointer;
                    transition: all 0.2s;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
                }
                
                .mpd-preview-btn .preview-icon {
                    font-size: 20px;  /* ✅ 14px × 1.5 ≈ 21px */
                    line-height: 1;
                }
                
                .mpd-preview-btn:hover {
                    background: linear-gradient(135deg, rgba(102, 126, 234, 0.35), rgba(118, 75, 162, 0.35));
                    border-color: rgba(102, 126, 234, 0.7);
                    transform: translateY(-2px);
                    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
                    color: #fff;
                }
                
                .mpd-preview-btn:active {
                    transform: translateY(-1px);
                }
                
                .mpd-close-btn {
                    background: none;
                    border: none;
                    color: #999;
                    font-size: 32px;  /* ✅ 24px × 1.5 ≈ 36px */
                    cursor: pointer;
                    padding: 0;
                    width: 44px;  /* ✅ 36px × 1.5 ≈ 54px，稍微调小 */
                    height: 44px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    border-radius: 6px;
                    transition: all 0.2s;
                    flex-shrink: 0;
                }
                
                .mpd-close-btn:hover {
                    background: rgba(255, 255, 255, 0.1);
                    color: #fff;
                    transform: rotate(90deg);
                }
                
                /* 材质球预览悬浮窗 */
                .mpd-preview-float {
                    position: absolute;
                    top: 70px;
                    right: -420px;  /* 默认在窗口右侧外部 */
                    width: 400px;
                    height: 480px;
                    background: linear-gradient(135deg, #1a1a1a 0%, #252525 100%);
                    border: 1px solid rgba(102, 126, 234, 0.3);
                    border-radius: 12px;
                    box-shadow: 0 15px 40px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(102, 126, 234, 0.2);
                    z-index: 100;
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                    transition: right 0.3s ease;
                }
                
                .mpd-preview-float-header {
                    padding: 12px 16px;
                    background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(118, 75, 162, 0.2));
                    border-bottom: 1px solid rgba(102, 126, 234, 0.3);
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    color: #6ec1ff;
                    font-size: 12px;
                    font-weight: 600;
                    letter-spacing: 0.5px;
                }
                
                .mpd-preview-close {
                    background: rgba(255, 255, 255, 0.05);
                    border: 1px solid rgba(255, 255, 255, 0.1);
                    color: #999;
                    font-size: 18px;
                    cursor: pointer;
                    padding: 0;
                    width: 28px;
                    height: 28px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    border-radius: 6px;
                    transition: all 0.2s;
                }
                
                .mpd-preview-close:hover {
                    background: rgba(244, 67, 54, 0.2);
                    border-color: rgba(244, 67, 54, 0.4);
                    color: #ff5252;
                }
                
                .mpd-preview-float-body {
                    flex: 1;
                    background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    padding: 20px;
                    border: 1px solid rgba(102, 126, 234, 0.1);
                    margin: 8px;
                    border-radius: 8px;
                }
                
                #material-preview-canvas {
                    width: 100%;
                    height: 100%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                }
                
                #material-preview-canvas canvas {
                    max-width: 100%;
                    max-height: 100%;
                    display: block;
                }
                
                /* ✅ 主体区域：参数列表占据全部宽度，靠左对齐 */
                .mpd-body {
                    flex: 1 !important;
                    overflow-y: auto;
                    background: rgba(25, 25, 30, 0.98);
                }
                
                .mpd-params-container {
                    padding: 16px;
                    max-width: 100%;
                    display: flex;
                    flex-direction: column;
                    align-items: flex-start;  /* ✅ 靠左对齐 */
                }
                
                .mpd-body::-webkit-scrollbar {
                    width: 8px;
                }
                
                .mpd-body::-webkit-scrollbar-track {
                    background: rgba(0, 0, 0, 0.2);
                    border-radius: 4px;
                }
                
                .mpd-body::-webkit-scrollbar-thumb {
                    background: rgba(102, 126, 234, 0.4);
                    border-radius: 4px;
                }
                
                .mpd-body::-webkit-scrollbar-thumb:hover {
                    background: rgba(102, 126, 234, 0.6);
                }
                
                .mpd-footer {
                    padding: 16px 24px;
                    border-top: 1px solid #444;
                    background: #1a1a1a;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    gap: 10px;
                    color: #888;
                    font-size: 15px;  /* ✅ 12px × 1.5 ≈ 18px */
                    flex-shrink: 0;
                }
                
                .mpd-footer-icon {
                    font-size: 20px;  /* ✅ 14px × 1.5 ≈ 21px */
                }
                
                /* 贴图信息容器样式 */
                .mpd-textures-container {
                    padding: 16px;
                }
                
                .loading-textures {
                    text-align: center;
                    color: #999;
                    padding: 40px;
                    font-size: 14px;
                }
                
                .textures-category {
                    margin-bottom: 24px;
                }
                
                .textures-category-title {
                    font-size: 16px;
                    font-weight: 600;
                    color: #6ec1ff;
                    margin: 0 0 12px 0;
                    padding: 10px 12px;
                    background: rgba(102, 126, 234, 0.1);
                    border-left: 3px solid #667eea;
                    border-radius: 4px;
                }
                
                .textures-list {
                    display: grid;
                    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
                    gap: 12px;
                }
                
                .texture-item {
                    background: rgba(30, 30, 35, 0.8);
                    border: 1px solid #444;
                    border-radius: 8px;
                    padding: 12px;
                    transition: all 0.2s;
                }
                
                .texture-item:hover {
                    background: rgba(40, 40, 45, 0.9);
                    border-color: #667eea;
                    transform: translateY(-2px);
                    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
                }
                
                .texture-item.existing {
                    border-left: 3px solid #4caf50;
                }
                
                .texture-item.new {
                    border-left: 3px solid #ff9800;
                }
                
                .texture-preview {
                    width: 100%;
                    height: 120px;
                    background: #0a0a0a;
                    border-radius: 4px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    margin-bottom: 8px;
                    overflow: hidden;
                    border: 1px solid #333;
                }
                
                .texture-preview img {
                    max-width: 100%;
                    max-height: 100%;
                    object-fit: contain;
                }
                
                .texture-preview .no-preview {
                    color: #666;
                    font-size: 12px;
                }
                
                .texture-name {
                    font-size: 13px;
                    color: #fff;
                    font-weight: 500;
                    margin-bottom: 4px;
                    word-break: break-all;
                }
                
                .texture-type {
                    font-size: 11px;
                    color: #999;
                    margin-bottom: 6px;
                }
                
                .texture-status {
                    font-size: 11px;
                    padding: 4px 8px;
                    border-radius: 4px;
                    display: inline-block;
                    font-weight: 500;
                }
                
                .texture-status.existing {
                    background: rgba(76, 175, 80, 0.2);
                    color: #81c784;
                }
                
                .texture-status.new {
                    background: rgba(255, 152, 0, 0.2);
                    color: #ffb74d;
                }
                
                .no-textures {
                    text-align: center;
                    color: #666;
                    padding: 40px;
                    font-size: 14px;
                }
            </style>
        `;
        
        // 绑定关闭事件
        const closeBtn = dialog.querySelector('.mpd-close-btn');
        closeBtn.addEventListener('click', () => {
            this.cleanup3DPreview();
            dialog.remove();
        });
        
        // 绑定Tab切换事件
        const tabBtns = dialog.querySelectorAll('.mpd-tab-btn');
        const tabContents = dialog.querySelectorAll('.mpd-tab-content');
        
        tabBtns.forEach(btn => {
            btn.addEventListener('click', () => {
                const targetTab = btn.getAttribute('data-tab');
                
                // 更新按钮状态
                tabBtns.forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
                
                // 更新内容显示
                tabContents.forEach(content => {
                    if (content.id === `tab-${targetTab}`) {
                        content.classList.add('active');
                        
                        // 如果切换到贴图tab且还未加载，则加载贴图信息
                        if (targetTab === 'texture' && !content.dataset.loaded) {
                            content.dataset.loaded = 'true';
                            this.loadTextureInfo(materialInfo, textureInfo, content);
                        }
                    } else {
                        content.classList.remove('active');
                    }
                });
            });
        });
        
        // 绑定3D预览按钮事件
        const previewBtn = dialog.querySelector('.mpd-preview-btn');
        const previewFloat = dialog.querySelector('.mpd-preview-float');
        const previewCloseBtn = dialog.querySelector('.mpd-preview-close');
        
        previewBtn.addEventListener('click', () => {
            if (previewFloat.style.display === 'none') {
                // ✅ 先清理旧的预览，防止叠加
                this.cleanup3DPreview();
                
                previewFloat.style.display = 'flex';
                // 触发滑入动画
                setTimeout(() => {
                    previewFloat.style.right = '20px';  // 滑入到可见位置
                }, 10);
                // 初始化3D预览（延迟确保DOM渲染完成）
                setTimeout(() => {
                    this.init3DPreview(tempMaterial);
                }, 150);
            } else {
                // 清理预览
                this.cleanup3DPreview();
                // 滑出动画
                previewFloat.style.right = '-420px';
                setTimeout(() => {
                    previewFloat.style.display = 'none';
                }, 300);
            }
        });
        
        previewCloseBtn.addEventListener('click', () => {
            // 清理预览
            this.cleanup3DPreview();
            // 滑出动画
            previewFloat.style.right = '-420px';
            setTimeout(() => {
                previewFloat.style.display = 'none';
            }, 300);
        });
        
        dialog.addEventListener('click', (e) => {
            if (e.target === dialog) {
                this.cleanup3DPreview();
                dialog.remove();
            }
        });
        
        document.body.appendChild(dialog);
    }
    
    /**
     * 加载并显示贴图信息
     * @param {Object} materialInfo - 材质信息
     * @param {Object} textureInfo - 当前材质的贴图信息
     * @param {HTMLElement} container - 容器元素
     */
    async loadTextureInfo(materialInfo, textureInfo, container) {
        const texturesContainer = container.querySelector('.mpd-textures-container');
        texturesContainer.innerHTML = '<div class="loading-textures">正在加载贴图信息...</div>';
        
        try {
            // 1. 获取项目贴图库的所有贴图
            const response = await fetch(`/api/projects/${this.projectId}/textures`);
            const result = await response.json();
            
            if (!result.success) {
                throw new Error(result.error || '加载贴图库失败');
            }
            
            const libraryTextures = result.data || [];
            
            console.log('📚 [贴图信息] 贴图库数据:', libraryTextures);
            console.log('🎨 [贴图信息] 当前材质贴图:', textureInfo);
            
            // 2. 提取当前材质使用的贴图
            const currentTextures = this.extractCurrentTextures(textureInfo);
            
            console.log('📝 [贴图信息] 提取的当前贴图:', currentTextures);
            
            if (currentTextures.length === 0) {
                texturesContainer.innerHTML = '<div class="no-textures">该材质没有使用任何贴图</div>';
                return;
            }
            
            // 3. 分类：同名贴图 vs 新贴图
            const existingTextures = [];
            const newTextures = [];
            
            for (const currentTex of currentTextures) {
                // 检查贴图库中是否存在同名贴图（基于originalFileName匹配）
                const matchingTexture = libraryTextures.find(libTex => {
                    const libName = libTex.originalFileName || libTex.fileName || libTex.name;
                    return libName === currentTex.name;
                });
                
                if (matchingTexture) {
                    existingTextures.push({
                        ...currentTex,
                        matchingTexture
                    });
                } else {
                    newTextures.push(currentTex);
                }
            }
            
            console.log('✅ [贴图信息] 同名贴图:', existingTextures);
            console.log('📦 [贴图信息] 新贴图:', newTextures);
            
            // 4. 生成HTML
            const html = this.generateTextureInfoHTML(existingTextures, newTextures);
            texturesContainer.innerHTML = html;
            
        } catch (error) {
            console.error('❌ [贴图信息] 加载失败:', error);
            texturesContainer.innerHTML = `<div class="no-textures">加载贴图信息失败: ${error.message}</div>`;
        }
    }
    
    /**
     * 从贴图信息中提取当前使用的贴图列表
     * @param {Object} textureInfo - 贴图信息
     * @returns {Array} 贴图列表
     */
    extractCurrentTextures(textureInfo) {
        const textures = [];
        
        if (!textureInfo) {
            return textures;
        }
        
        const textureTypes = {
            map: '基础颜色贴图',
            normalMap: '法线贴图',
            bumpMap: '凹凸贴图',
            aoMap: 'AO贴图',
            roughnessMap: '粗糙度贴图',
            metalnessMap: '金属度贴图',
            emissiveMap: '自发光贴图',
            alphaMap: '透明度贴图',
            displacementMap: '置换贴图',
            specularMap: '镜面反射贴图',
            lightMap: '光照贴图'
        };
        
        for (const [key, label] of Object.entries(textureTypes)) {
            const textureData = textureInfo[key];
            if (textureData) {
                // 从多个来源提取贴图名称
                let textureName = '';
                
                // 1. 优先使用存储的name字段（来自extractTexturesFromMaterial）
                if (textureData.name) {
                    textureName = textureData.name;
                } 
                // 2. 从texture对象的name属性获取
                else if (textureData.texture && textureData.texture.name) {
                    textureName = textureData.texture.name;
                } 
                // 3. 从URL中提取文件名
                else if (textureData.url) {
                    const urlParts = textureData.url.split('/');
                    textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                    // 移除可能的查询参数
                    textureName = textureName.split('?')[0];
                }
                // 4. 从image的src中提取
                else if (textureData.image && textureData.image.src) {
                    const urlParts = textureData.image.src.split('/');
                    textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                    textureName = textureName.split('?')[0];
                }
                // 5. 从userData获取
                else if (textureData.texture && textureData.texture.userData && textureData.texture.userData.fileName) {
                    textureName = textureData.texture.userData.fileName;
                }
                
                // 如果贴图名称包含材质名前缀，尝试提取原始文件名
                // 例如: "Material_map" -> "map" 或保留原名
                // 但如果名称看起来像是文件名（包含扩展名），则保留
                if (textureName && !textureName.includes('.')) {
                    // 如果没有扩展名，可能是Three.js生成的名称
                    // 我们保持原样，因为这可能是有意义的
                }
                
                textures.push({
                    type: key,
                    typeLabel: label,
                    name: textureName,
                    textureData: textureData
                });
            }
        }
        
        return textures;
    }
    
    /**
     * 生成贴图信息HTML
     * @param {Array} existingTextures - 同名贴图列表
     * @param {Array} newTextures - 新贴图列表
     * @returns {string} HTML字符串
     */
    generateTextureInfoHTML(existingTextures, newTextures) {
        let html = '';
        
        // 同名贴图（可复用）
        if (existingTextures.length > 0) {
            html += `
                <div class="textures-category">
                    <h3 class="textures-category-title">✅ 贴图库有同名贴图 (${existingTextures.length})</h3>
                    <div class="textures-list">
            `;
            
            for (const tex of existingTextures) {
                html += this.generateTextureItemHTML(tex, true);
            }
            
            html += `
                    </div>
                </div>
            `;
        }
        
        // 新贴图
        if (newTextures.length > 0) {
            html += `
                <div class="textures-category">
                    <h3 class="textures-category-title">📦 新建贴图 (${newTextures.length})</h3>
                    <div class="textures-list">
            `;
            
            for (const tex of newTextures) {
                html += this.generateTextureItemHTML(tex, false);
            }
            
            html += `
                    </div>
                </div>
            `;
        }
        
        return html;
    }
    
    /**
     * 生成单个贴图项HTML
     * @param {Object} textureData - 贴图数据
     * @param {boolean} isExisting - 是否为已存在的贴图
     * @returns {string} HTML字符串
     */
    generateTextureItemHTML(textureData, isExisting) {
        const statusClass = isExisting ? 'existing' : 'new';
        const statusText = isExisting ? '已存在（复用）' : '新建贴图';
        
        // 尝试获取贴图预览
        let previewHTML = '<div class="no-preview">无预览</div>';
        
        // 使用textureData字段（包含了texture对象和image对象）
        if (textureData.textureData) {
            const data = textureData.textureData;
            
            // 1. 优先使用url字段
            if (data.url) {
                previewHTML = `<img src="${data.url}" alt="${textureData.name}">`;
            }
            // 2. 使用image对象
            else if (data.image) {
                const img = data.image;
                if (img.currentSrc || img.src) {
                    previewHTML = `<img src="${img.currentSrc || img.src}" alt="${textureData.name}">`;
                } else if (img instanceof HTMLCanvasElement) {
                    // Canvas元素转为data URL
                    try {
                        const dataUrl = img.toDataURL();
                        previewHTML = `<img src="${dataUrl}" alt="${textureData.name}">`;
                    } catch (e) {
                        console.warn('无法转换Canvas为预览图:', e);
                    }
                }
            }
            // 3. 从texture对象的image属性获取
            else if (data.texture && data.texture.image) {
                const img = data.texture.image;
                if (img.currentSrc || img.src) {
                    previewHTML = `<img src="${img.currentSrc || img.src}" alt="${textureData.name}">`;
                }
            }
        }
        
        // 如果有匹配的贴图库贴图，使用贴图库的预览
        if (isExisting && textureData.matchingTexture) {
            const libTex = textureData.matchingTexture;
            const texturePath = `/api/projects/${this.projectId}/textures/${libTex.id}/file`;
            previewHTML = `<img src="${texturePath}" alt="${textureData.name}">`;
        }
        
        return `
            <div class="texture-item ${statusClass}">
                <div class="texture-preview">
                    ${previewHTML}
                </div>
                <div class="texture-name">${textureData.name || '未命名'}</div>
                <div class="texture-type">${textureData.typeLabel}</div>
                <div class="texture-status ${statusClass}">${statusText}</div>
            </div>
        `;
    }
    
    /**
     * 生成材质参数HTML（完整版 - 包含所有参数）
     * @param {Object} properties - 材质属性
     * @param {Object} textureInfo - 贴图信息
     * @returns {string} HTML字符串
     */
    generateMaterialParamsHTML(properties, textureInfo) {
        let html = '<div class="mpd-params-readonly">';
        
        // 📌 参数分组（4栏布局）
        const paramGroups = {
            '🎨 基础参数': [
                { key: 'type', label: '源材质类型', type: 'text' },
                { key: 'color', label: '基础颜色', type: 'color' },
                { key: 'metalness', label: '金属度', type: 'slider', range: [0, 1] },
                { key: 'roughness', label: '粗糙度', type: 'slider', range: [0, 1] },
                { key: 'opacity', label: '不透明度', type: 'slider', range: [0, 1] },
                { key: 'transparent', label: '透明模式', type: 'boolean' },
                { key: 'emissive', label: '自发光颜色', type: 'color' },
                { key: 'emissiveIntensity', label: '自发光强度', type: 'slider', range: [0, 2] },
            ]
        };
        
        // ⚙️ 其他参数（放到最后）
        const otherParams = [
            { key: 'side', label: '面渲染', type: 'side' },
        ];
        
        // 渲染参数组（带展开/折叠功能）
        let groupIndex = 0;
        for (const [groupName, params] of Object.entries(paramGroups)) {
            // 检查该组是否有可显示的参数
            const hasVisibleParams = params.some(param => {
                const value = properties[param.key];
                return value !== undefined && value !== null;
            });
            
            if (!hasVisibleParams) {
                continue; // 跳过空组
            }
            
            const groupId = `mpd-group-${groupIndex}`;
            const isDefaultOpen = groupIndex === 0; // 只有第一个（基础参数）默认展开
            
            html += `<div class="mpd-param-group">`;
            html += `
                <h3 class="mpd-param-group-title mpd-collapsible" onclick="this.classList.toggle('mpd-collapsed'); const content = document.getElementById('${groupId}'); const icon = this.querySelector('.mpd-collapse-icon'); if (this.classList.contains('mpd-collapsed')) { content.style.display = 'none'; icon.textContent = '▶'; } else { content.style.display = 'block'; icon.textContent = '▼'; }">
                    <span class="mpd-collapse-icon">${isDefaultOpen ? '▼' : '▶'}</span>
                    <span>${groupName}</span>
                </h3>
            `;
            html += `<div id="${groupId}" style="display: ${isDefaultOpen ? 'block' : 'none'};">`;
            
            for (const param of params) {
                const value = properties[param.key];
                if (value !== undefined && value !== null) {
                    html += this.renderReadOnlyParam(param, value, properties);
                }
            }
            
            html += `</div></div>`;
            
            if (!isDefaultOpen) {
                // 给非默认展开的组添加折叠样式
                html = html.replace(`<h3 class="mpd-param-group-title mpd-collapsible"`, 
                                  `<h3 class="mpd-param-group-title mpd-collapsible mpd-collapsed"`);
            }
            
            groupIndex++;
        }
        
        // 🖼️ 纹理贴图及各纹理强度（计算贴图数量）
        const textureNames = {
            map: { name: '基础颜色贴图', intensity: null },
            normalMap: { name: '法线贴图', intensity: 'normalScale' },
            bumpMap: { name: '凹凸贴图', intensity: 'bumpScale' },
            aoMap: { name: 'AO贴图', intensity: 'aoMapIntensity' },
            roughnessMap: { name: '粗糙度贴图', intensity: null },
            metalnessMap: { name: '金属度贴图', intensity: null },
            emissiveMap: { name: '发光贴图', intensity: 'emissiveIntensity' },
            alphaMap: { name: '透明贴图', intensity: null },
            displacementMap: { name: '置换贴图', intensity: 'displacementScale' }
        };
        
        // 统计实际有贴图的数量
        let textureCount = 0;
        if (textureInfo) {
            for (const key of Object.keys(textureNames)) {
                if (textureInfo[key]) {
                    textureCount++;
                }
            }
        }
        
        html += `<div class="mpd-param-group">`;
        html += `
            <h3 class="mpd-param-group-title mpd-collapsible mpd-collapsed" onclick="this.classList.toggle('mpd-collapsed'); const content = document.getElementById('mpd-group-textures'); const icon = this.querySelector('.mpd-collapse-icon'); if (this.classList.contains('mpd-collapsed')) { content.style.display = 'none'; icon.textContent = '▶'; } else { content.style.display = 'block'; icon.textContent = '▼'; }">
                <span class="mpd-collapse-icon">▶</span>
                <span>🖼️ 纹理贴图 (${textureCount}/${Object.keys(textureNames).length})</span>
            </h3>
        `;
        html += `<div id="mpd-group-textures" style="display: none;">`;
        
        // ✅ 遍历所有贴图类型，无论是否有贴图都显示
        for (const [key, textureConfig] of Object.entries(textureNames)) {
            const hasTexture = textureInfo && textureInfo[key];
            const textureName = textureConfig.name;
            const intensityKey = textureConfig.intensity;
            
            html += `
                <div class="mpd-param-item">
                    <div class="mpd-param-label">${textureName}</div>
                    <div class="mpd-param-value">`;
            
            // ✅ 简化显示：只显示贴图名称或"无"
            if (hasTexture) {
                const texture = textureInfo[key];
                const displayName = texture.name || '有';
                
                html += `
                    <span style="
                        color: #4a9eff; 
                        font-weight: 600;
                        font-family: 'Courier New', monospace;
                    ">${displayName}</span>
                `;
            } else {
                html += `
                    <span style="
                        color: #888; 
                        font-weight: 500;
                    ">无</span>
                `;
            }
            
            // 🎚️ 显示纹理强度（无论是否有贴图，只要有强度参数就显示）
            if (intensityKey && properties[intensityKey] !== undefined) {
                const intensityValue = properties[intensityKey];
                let displayValue = intensityValue;
                
                // 特殊处理 normalScale (Vector2)
                if (intensityKey === 'normalScale' && typeof intensityValue === 'object') {
                    displayValue = `(${intensityValue.x.toFixed(2)}, ${intensityValue.y.toFixed(2)})`;
                } else if (typeof intensityValue === 'number') {
                    displayValue = intensityValue.toFixed(2);
                }
                
                html += `
                    <span style="
                        color: #ffa726; 
                        font-weight: 500;
                        background: rgba(255, 167, 38, 0.12);
                        padding: 4px 10px;
                        border-radius: 4px;
                        font-family: 'Courier New', monospace;
                        margin-left: 12px;
                        font-size: 14px;
                    ">强度: ${displayValue}</span>
                `;
            }
            
            html += `
                    </div>
                </div>
            `;
        }
        
        html += `</div></div>`;
        
        // 📍 UV信息（如果有贴图）
        console.log('🔍 [UV信息] textureInfo:', textureInfo);
        console.log('🔍 [UV信息] 贴图数量:', textureInfo ? Object.keys(textureInfo).length : 0);
        
        if (textureInfo && Object.keys(textureInfo).length > 0) {
            // 获取第一个有效贴图的UV信息（通常所有贴图共享UV设置）
            let firstTexture = null;
            for (const key of Object.keys(textureInfo)) {
                if (textureInfo[key]) {
                    firstTexture = textureInfo[key];
                    console.log(`🔍 [UV信息] 使用贴图 "${key}" 的UV数据:`, firstTexture);
                    break;
                }
            }
            
            if (!firstTexture) {
                console.warn('⚠️ [UV信息] 找不到有效的贴图数据');
            }
            
            if (firstTexture) {
                html += `<div class="mpd-param-group">`;
                html += `
                    <h3 class="mpd-param-group-title mpd-collapsible mpd-collapsed" onclick="this.classList.toggle('mpd-collapsed'); const content = document.getElementById('mpd-group-uv'); const icon = this.querySelector('.mpd-collapse-icon'); if (this.classList.contains('mpd-collapsed')) { content.style.display = 'none'; icon.textContent = '▶'; } else { content.style.display = 'block'; icon.textContent = '▼'; }">
                        <span class="mpd-collapse-icon">▶</span>
                        <span>📍 UV信息</span>
                    </h3>
                `;
                html += `<div id="mpd-group-uv" style="display: none;">`;
                
                // UV通道
                html += `
                    <div class="mpd-param-item">
                        <div class="mpd-param-label">UV 通道</div>
                        <div class="mpd-param-value">
                            <span style="
                                color: rgba(255, 255, 255, 0.85); 
                                font-weight: 500;
                                background: rgba(102, 126, 234, 0.1);
                                padding: 6px 14px;
                                border-radius: 6px;
                            ">通道 0 (默认)</span>
                        </div>
                    </div>
                `;
                
                // UV重复
                const repeatX = firstTexture.repeat?.x ?? 1;
                const repeatY = firstTexture.repeat?.y ?? 1;
                html += `
                    <div class="mpd-param-item">
                        <div class="mpd-param-label">UV 平铺</div>
                        <div class="mpd-param-value">
                            <span style="
                                color: rgba(255, 255, 255, 0.85); 
                                font-weight: 500;
                                font-family: 'Courier New', monospace;
                            ">(${repeatX.toFixed(2)}, ${repeatY.toFixed(2)})</span>
                        </div>
                    </div>
                `;
                
                // UV偏移
                const offsetX = firstTexture.offset?.x ?? 0;
                const offsetY = firstTexture.offset?.y ?? 0;
                html += `
                    <div class="mpd-param-item">
                        <div class="mpd-param-label">UV 偏移</div>
                        <div class="mpd-param-value">
                            <span style="
                                color: rgba(255, 255, 255, 0.85); 
                                font-weight: 500;
                                font-family: 'Courier New', monospace;
                            ">(${offsetX.toFixed(2)}, ${offsetY.toFixed(2)})</span>
                        </div>
                    </div>
                `;
                
                // UV旋转
                const rotation = firstTexture.rotation ?? 0;
                const rotationDeg = (rotation * 180 / Math.PI).toFixed(1);
                html += `
                    <div class="mpd-param-item">
                        <div class="mpd-param-label">UV 旋转</div>
                        <div class="mpd-param-value">
                            <span style="
                                color: rgba(255, 255, 255, 0.85); 
                                font-weight: 500;
                                font-family: 'Courier New', monospace;
                            ">${rotationDeg}°</span>
                        </div>
                    </div>
                `;
                
                // UV中心点
                const centerX = firstTexture.center?.x ?? 0.5;
                const centerY = firstTexture.center?.y ?? 0.5;
                html += `
                    <div class="mpd-param-item">
                        <div class="mpd-param-label">UV 中心点</div>
                        <div class="mpd-param-value">
                            <span style="
                                color: rgba(255, 255, 255, 0.85); 
                                font-weight: 500;
                                font-family: 'Courier New', monospace;
                            ">(${centerX.toFixed(2)}, ${centerY.toFixed(2)})</span>
                        </div>
                    </div>
                `;
                
                // UV环绕模式
                html += `
                    <div class="mpd-param-item">
                        <div class="mpd-param-label">环绕模式</div>
                        <div class="mpd-param-value">
                            <span style="
                                color: rgba(255, 255, 255, 0.85); 
                                font-weight: 500;
                                background: rgba(102, 126, 234, 0.1);
                                padding: 6px 14px;
                                border-radius: 6px;
                            ">重复 (默认)</span>
                        </div>
                    </div>
                `;
                
                html += `</div></div>`;
            }
        }
        
        // ⚙️ 其他参数（放到最后）
        const hasOtherParams = otherParams.some(param => {
            const value = properties[param.key];
            return value !== undefined && value !== null;
        });
        
        if (hasOtherParams) {
            html += `<div class="mpd-param-group">`;
            html += `
                <h3 class="mpd-param-group-title mpd-collapsible mpd-collapsed" onclick="this.classList.toggle('mpd-collapsed'); const content = document.getElementById('mpd-group-other'); const icon = this.querySelector('.mpd-collapse-icon'); if (this.classList.contains('mpd-collapsed')) { content.style.display = 'none'; icon.textContent = '▶'; } else { content.style.display = 'block'; icon.textContent = '▼'; }">
                    <span class="mpd-collapse-icon">▶</span>
                    <span>⚙️ 其他</span>
                </h3>
            `;
            html += `<div id="mpd-group-other" style="display: none;">`;
            
            for (const param of otherParams) {
                const value = properties[param.key];
                if (value !== undefined && value !== null) {
                    html += this.renderReadOnlyParam(param, value, properties);
                }
            }
            
            html += `</div></div>`;
        }
        
        html += '</div>';
        
        // ✅ 添加内部样式（使用 mpd- 前缀完全隔离）
        html += `
            <style>
                .mpd-params-readonly {
                    display: flex;
                    flex-direction: column;
                    gap: 10px;
                    width: 100% !important;
                    max-width: 100% !important;
                }
                
                .mpd-param-group {
                    background: rgba(255, 255, 255, 0.03);
                    border: 1px solid rgba(102, 126, 234, 0.15);
                    border-radius: 8px;
                    padding: 0;
                    overflow: hidden;
                    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
                    width: 100% !important;
                    max-width: 100% !important;
                }
                
                .mpd-param-group-title {
                    margin: 0;
                    font-size: 16px;  /* ✅ 11px × 1.5 = 16.5px ≈ 16px */
                    font-weight: 700;
                    color: #6ec1ff;
                    padding: 14px 16px;  /* ✅ 增加内边距 */
                    background: linear-gradient(135deg, rgba(102, 126, 234, 0.15), rgba(118, 75, 162, 0.15));
                    border-bottom: 1px solid rgba(102, 126, 234, 0.25);
                    text-transform: uppercase;
                    letter-spacing: 1.0px;  /* ✅ 增加字间距 */
                    display: flex;
                    align-items: center;
                    gap: 10px;  /* ✅ 增加图标间距 */
                }
                
                /* ✅ 可展开/折叠样式 */
                .mpd-param-group-title.mpd-collapsible {
                    cursor: pointer;
                    user-select: none;
                }
                
                .mpd-param-group-title.mpd-collapsible:hover {
                    background: linear-gradient(135deg, rgba(102, 126, 234, 0.25), rgba(118, 75, 162, 0.25));
                }
                
                .mpd-collapse-icon {
                    font-size: 14px;
                    transition: transform 0.2s;
                    display: inline-block;
                }
                
                .mpd-param-group-title.mpd-collapsed .mpd-collapse-icon {
                    transform: rotate(0deg);
                }
                
                .mpd-param-group-title:not(.mpd-collapsed) .mpd-collapse-icon {
                    transform: rotate(0deg);
                }
                
                .mpd-param-item {
                    display: flex;
                    align-items: center;
                    padding: 4px 6px;  /* ✅ 减小到1/3 */
                    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
                    transition: all 0.2s;
                    min-height: 17px;  /* ✅ 减小到1/3 */
                    width: 100% !important;
                }
                
                .mpd-param-item:hover {
                    background: rgba(102, 126, 234, 0.08);
                }
                
                .mpd-param-item:last-child {
                    border-bottom: none;
                }
                
                .mpd-param-label {
                    color: rgba(255, 255, 255, 0.75);
                    font-size: 16px;  /* ✅ 11px × 1.5 = 16.5px ≈ 16px */
                    font-weight: 600;
                    width: 120px;  /* ✅ 增加标签宽度 */
                    text-align: left;
                    flex-shrink: 0;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    white-space: nowrap;
                    margin-right: 16px;  /* ✅ 增加间距 */
                }
                
                .mpd-param-value {
                    color: rgba(255, 255, 255, 0.95);
                    font-size: 16px;  /* ✅ 11px × 1.5 = 16.5px ≈ 16px */
                    display: flex;
                    align-items: center;
                    gap: 10px;  /* ✅ 增加元素间距 */
                    font-family: 'Courier New', monospace;
                    flex: 1;  /* ✅ 占据剩余空间 */
                    min-width: 0;  /* ✅ 允许弹性收缩 */
                }
                
                .mpd-param-value > span {
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    max-width: 200px;  /* ✅ 限制文本最大宽度 */
                }
                
                .mpd-color-preview {
                    width: 40px;  /* ✅ 28px × 1.5 ≈ 40px */
                    height: 40px;
                    border-radius: 6px;  /* ✅ 增大圆角 */
                    border: 2px solid rgba(255, 255, 255, 0.2);
                    box-shadow: 0 2px 8px rgba(0,0,0,0.4), inset 0 0 0 1px rgba(255,255,255,0.15);
                    flex-shrink: 0;
                }
                
                .mpd-slider-preview {
                    width: 100px;  /* ✅ 70px × 1.5 ≈ 100px */
                    height: 8px;  /* ✅ 5px × 1.5 ≈ 8px */
                    background: rgba(0, 0, 0, 0.4);
                    border-radius: 4px;  /* ✅ 增大圆角 */
                    position: relative;
                    overflow: hidden;
                    box-shadow: inset 0 1px 3px rgba(0,0,0,0.5);
                    flex-shrink: 0;
                }
                
                .mpd-slider-fill {
                    height: 100%;
                    background: linear-gradient(90deg, #4a9eff, #6ec1ff);
                    border-radius: 4px;
                    box-shadow: 0 0 8px rgba(74, 158, 255, 0.7);  /* ✅ 增强发光 */
                }
            </style>
        `;
        
        return html;
    }
    
    /**
     * 渲染只读参数（支持多种类型）
     * @param {Object} param - 参数定义
     * @param {*} value - 参数值
     * @returns {string} HTML字符串
     */
    renderReadOnlyParam(param, value, properties = {}) {
        let valueHTML = '';
        
        if (param.type === 'color') {
            // 颜色参数
            const colorValue = typeof value === 'string' ? value : '#ffffff';
            valueHTML = `
                <div class="mpd-color-preview" style="background: ${colorValue};"></div>
                <span style="color: #fff; font-weight: 500;">${colorValue}</span>
            `;
        } else if (param.type === 'slider') {
            // 滑块参数
            const [min, max] = param.range;
            const numValue = typeof value === 'number' ? value : 0;
            const percentage = Math.max(0, Math.min(100, ((numValue - min) / (max - min)) * 100));
            
            valueHTML = `
                <div class="mpd-slider-preview">
                    <div class="mpd-slider-fill" style="width: ${percentage}%;"></div>
                </div>
                <span style="color: #4a9eff; font-weight: 600;">${numValue.toFixed(3)}</span>
            `;
            
            // 🎯 添加转换说明（如果是从Phong材质转换来的）
            if (param.key === 'roughness' && properties._convertedFromShininess !== undefined) {
                valueHTML += `
                    <span style="
                        color: #ff9800; 
                        font-weight: 500;
                        font-size: 13px;
                        margin-left: 10px;
                        opacity: 0.8;
                    ">← 由高光度 ${properties._convertedFromShininess.toFixed(1)} 转换</span>
                `;
            }
            
            if (param.key === 'metalness' && properties._convertedFromSpecular !== undefined) {
                valueHTML += `
                    <span style="
                        color: #ff9800; 
                        font-weight: 500;
                        font-size: 13px;
                        margin-left: 10px;
                        opacity: 0.8;
                    ">← 由高光颜色 ${properties._convertedFromSpecular} 转换</span>
                `;
            }
        } else if (param.type === 'boolean') {
            // 布尔参数
            const boolValue = value === true || value === 'true';
            const icon = boolValue ? '✓' : '✗';
            const color = boolValue ? '#4caf50' : '#666';
            const text = boolValue ? '启用' : '禁用';
            valueHTML = `
                <span style="
                    color: ${color}; 
                    font-weight: 600;
                    background: ${boolValue ? 'rgba(76, 175, 80, 0.15)' : 'rgba(150, 150, 150, 0.15)'};
                    padding: 6px 14px;
                    border-radius: 6px;
                    display: inline-flex;
                    align-items: center;
                    gap: 8px;
                ">
                    <span style="font-size: 20px;">${icon}</span>
                    <span>${text}</span>
                </span>
            `;
        } else if (param.type === 'text') {
            // 文本参数
            valueHTML = `
                <span style="
                    color: rgba(255, 255, 255, 0.85); 
                    font-weight: 500;
                    background: rgba(102, 126, 234, 0.1);
                    padding: 6px 14px;
                    border-radius: 6px;
                ">${value}</span>
            `;
        } else if (param.type === 'vector2') {
            // Vector2 参数（如法线强度）
            if (value && typeof value === 'object') {
                const x = value.x !== undefined ? value.x.toFixed(2) : '0.00';
                const y = value.y !== undefined ? value.y.toFixed(2) : '0.00';
                valueHTML = `
                    <span style="
                        color: rgba(255, 255, 255, 0.85); 
                        font-weight: 500;
                        font-family: 'Courier New', monospace;
                    ">X: ${x}, Y: ${y}</span>
                `;
            } else {
                valueHTML = `<span style="color: #888;">未设置</span>`;
            }
        } else if (param.type === 'side') {
            // 面渲染模式
            let sideText = '未知';
            let sideIcon = '❓';
            if (value === 0) { // THREE.FrontSide
                sideText = '正面';
                sideIcon = '▶️';
            } else if (value === 1) { // THREE.BackSide
                sideText = '背面';
                sideIcon = '◀️';
            } else if (value === 2) { // THREE.DoubleSide
                sideText = '双面';
                sideIcon = '◀️▶️';
            }
            valueHTML = `
                <span style="
                    color: rgba(255, 255, 255, 0.85); 
                    font-weight: 500;
                    background: rgba(102, 126, 234, 0.15);
                    padding: 6px 14px;
                    border-radius: 6px;
                    display: inline-flex;
                    align-items: center;
                    gap: 8px;
                ">
                    <span style="font-size: 20px;">${sideIcon}</span>
                    <span>${sideText}</span>
                </span>
            `;
        } else {
            // 其他参数
            valueHTML = `<span style="color: #fff; font-weight: 500;">${value}</span>`;
        }
        
        return `
            <div class="mpd-param-item">
                <div class="mpd-param-label">${param.label}</div>
                <div class="mpd-param-value">${valueHTML}</div>
            </div>
        `;
    }
    
    /**
     * 初始化3D预览
     * @param {THREE.Material} material - 材质对象
     */
    init3DPreview(material) {
        const container = document.getElementById('material-preview-canvas');
        if (!container) {
            console.error('预览容器未找到');
            return;
        }
        
        // 获取容器的实际尺寸
        const width = container.clientWidth;
        const height = container.clientHeight;
        
        if (width === 0 || height === 0) {
            console.warn('容器尺寸为0，延迟初始化');
            setTimeout(() => this.init3DPreview(material), 100);
            return;
        }
        
        // 创建场景
        this.previewScene = new THREE.Scene();
        this.previewScene.background = new THREE.Color(0x0a0a0a);
        
        // 创建相机
        const aspect = width / height;
        this.previewCamera = new THREE.PerspectiveCamera(50, aspect, 0.1, 1000);
        this.previewCamera.position.set(0, 0, 2.5);
        this.previewCamera.lookAt(0, 0, 0);
        
        // 创建渲染器
        this.previewRenderer = new THREE.WebGLRenderer({ 
            antialias: true,
            alpha: false
        });
        this.previewRenderer.setSize(width, height);
        this.previewRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        container.appendChild(this.previewRenderer.domElement);
        
        // 创建球体预览（合适的大小）
        const geometry = new THREE.SphereGeometry(0.8, 64, 64);
        this.previewMesh = new THREE.Mesh(geometry, material);
        this.previewScene.add(this.previewMesh);
        
        // 添加光源
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        this.previewScene.add(ambientLight);
        
        const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight1.position.set(5, 5, 5);
        this.previewScene.add(directionalLight1);
        
        const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
        directionalLight2.position.set(-3, -3, -3);
        this.previewScene.add(directionalLight2);
        
        const rimLight = new THREE.DirectionalLight(0x4a9eff, 0.3);
        rimLight.position.set(0, 0, -5);
        this.previewScene.add(rimLight);
        
        // 启动动画循环
        this.startPreviewAnimation();
        
        // 添加窗口调整大小监听
        this.handleResize = () => {
            if (!container || !this.previewCamera || !this.previewRenderer) return;
            
            const newWidth = container.clientWidth;
            const newHeight = container.clientHeight;
            
            if (newWidth > 0 && newHeight > 0) {
                this.previewCamera.aspect = newWidth / newHeight;
                this.previewCamera.updateProjectionMatrix();
                this.previewRenderer.setSize(newWidth, newHeight);
            }
        };
        
        window.addEventListener('resize', this.handleResize);
    }
    
    /**
     * 启动预览动画
     */
    startPreviewAnimation() {
        const animate = () => {
            this.previewAnimationId = requestAnimationFrame(animate);
            
            if (this.previewMesh) {
                this.previewMesh.rotation.y += 0.005;
            }
            
            if (this.previewRenderer && this.previewScene && this.previewCamera) {
                this.previewRenderer.render(this.previewScene, this.previewCamera);
            }
        };
        animate();
    }
    
    /**
     * 清理3D预览
     */
    cleanup3DPreview() {
        // 移除窗口调整监听器
        if (this.handleResize) {
            window.removeEventListener('resize', this.handleResize);
            this.handleResize = null;
        }
        
        // 停止动画循环
        if (this.previewAnimationId) {
            cancelAnimationFrame(this.previewAnimationId);
            this.previewAnimationId = null;
        }
        
        // 清理网格和材质
        if (this.previewMesh) {
            if (this.previewMesh.geometry) {
                this.previewMesh.geometry.dispose();
            }
            if (this.previewMesh.material) {
                this.previewMesh.material.dispose();
            }
            this.previewMesh = null;
        }
        
        // 清理渲染器
        if (this.previewRenderer) {
            const canvas = this.previewRenderer.domElement;
            if (canvas && canvas.parentNode) {
                canvas.parentNode.removeChild(canvas);
            }
            this.previewRenderer.dispose();
            this.previewRenderer = null;
        }
        
        // ✅ 清空容器内所有残留元素（防止多次点击叠加）
        const container = document.getElementById('material-preview-canvas');
        if (container) {
            while (container.firstChild) {
                container.removeChild(container.firstChild);
            }
        }
        
        // 清理场景和相机
        if (this.previewScene) {
            this.previewScene.clear();
            this.previewScene = null;
        }
        
        this.previewCamera = null;
    }
    
    /**
     * 从材质使用信息中获取贴图信息
     * @param {Object} materialInfo - 材质使用信息
     * @returns {Object} 贴图信息
     */
    getMaterialTexturesFromUsage(materialInfo) {
        console.log(`🔍 [获取贴图] 开始获取材质贴图:`, {
            materialName: materialInfo.materialName,
            usagesCount: materialInfo.usages?.length
        });
        
        // 从第一个使用该材质的mesh中获取textures
        if (!materialInfo.usages || materialInfo.usages.length === 0) {
            console.warn('⚠️ [获取贴图] 没有usage信息');
            return {};
        }
        
        const firstUsage = materialInfo.usages[0];
        const model = this.cacheModels.find(m => m.id === firstUsage.modelId);
        
        if (!model) {
            console.warn('⚠️ [获取贴图] 找不到模型');
            return {};
        }
        
        console.log(`🔍 [获取贴图] 找到模型:`, {
            modelId: model.id,
            hasTexturesMap: !!model.texturesMap,
            texturesMapType: model.texturesMap?.constructor?.name,
            texturesMapSize: model.texturesMap?.size,
            texturesMapKeys: model.texturesMap instanceof Map ? 
                Array.from(model.texturesMap.keys()) : 
                Object.keys(model.texturesMap || {}),
            // ✅ 额外诊断信息
            texturesMapIsMap: model.texturesMap instanceof Map,
            texturesMapIsObject: model.texturesMap && typeof model.texturesMap === 'object',
            modelKeys: Object.keys(model)
        });
        
        // ✅ 如果texturesMap是Map，输出所有条目
        if (model.texturesMap instanceof Map && model.texturesMap.size > 0) {
            console.log(`🔍 [获取贴图] texturesMap内容:`, Array.from(model.texturesMap.entries()).map(([key, value]) => ({
                materialName: key,
                textureTypes: Object.keys(value || {})
            })));
        }
        
        // ✅ 优先从texturesMap获取完整的贴图数据（包含texture对象）
        if (model.texturesMap) {
            const materialName = materialInfo.materialName;
            const meshName = firstUsage.meshId; // ✅ 使用meshName作为key
            
            // 处理 Map 对象
            let fullTextures = null;
            if (model.texturesMap instanceof Map) {
                fullTextures = model.texturesMap.get(meshName); // ✅ 使用meshName查找
                console.log(`🔍 [获取贴图] 从Map获取mesh"${meshName}"的贴图 (材质: "${materialName}"):`, {
                    found: !!fullTextures,
                    keys: Array.from(model.texturesMap.keys())
                });
            } else {
                // 处理普通对象
                fullTextures = model.texturesMap[meshName]; // ✅ 使用meshName查找
                console.log(`🔍 [获取贴图] 从对象获取mesh"${meshName}"的贴图 (材质: "${materialName}"):`, {
                    found: !!fullTextures,
                    keys: Object.keys(model.texturesMap)
                });
            }
            
            if (fullTextures) {
                console.log(`✅ [获取贴图] 成功获取mesh"${meshName}"的完整贴图数据:`, {
                    textureTypes: Object.keys(fullTextures),
                    hasTextureObjects: Object.values(fullTextures).some(t => t?.texture)
                });
                return fullTextures;
            } else {
                console.warn(`⚠️ [获取贴图] texturesMap中找不到mesh"${meshName}"`);
            }
        }
        
        // ⚠️ 降级：从meshMaterials获取序列化后的贴图数据
        if (model.meshMaterials && model.meshMaterials[firstUsage.meshId]) {
            const meshData = model.meshMaterials[firstUsage.meshId];
            console.warn(`⚠️ [获取贴图] 降级使用序列化后的贴图数据（可能无法预览）`);
            return meshData.textures || {};
        }
        
        console.error('❌ [获取贴图] 无法获取任何贴图数据');
        return {};
    }
    
    /**
     * 从属性创建 Three.js 材质对象
     * @param {Object} properties - 材质属性
     * @param {Object} textureInfo - 贴图信息
     * @returns {THREE.Material} Three.js 材质对象
     */
    createThreeMaterialFromProperties(properties, textureInfo) {
        console.log(`🎨 [创建预览材质] 开始创建材质:`, {
            hasProperties: !!properties,
            hasTextureInfo: !!textureInfo,
            textureInfoKeys: textureInfo ? Object.keys(textureInfo) : []
        });
        
        // 固定使用 MeshPhysicalMaterial（物理材质）
        const material = new THREE.MeshPhysicalMaterial();
        
        // 应用基础属性
        if (properties.color) {
            material.color.set(properties.color);
        }
        if (properties.metalness !== undefined) {
            material.metalness = properties.metalness;
        }
        if (properties.roughness !== undefined) {
            material.roughness = properties.roughness;
        }
        if (properties.emissive) {
            material.emissive.set(properties.emissive);
        }
        if (properties.emissiveIntensity !== undefined) {
            material.emissiveIntensity = properties.emissiveIntensity;
        }
        if (properties.opacity !== undefined) {
            material.opacity = properties.opacity;
            // 🔧 修复：如果有贴图或opacity<1，都需要开启transparent
            material.transparent = properties.opacity < 1.0 || !!material.map || !!material.alphaMap;
        }
        if (properties.side !== undefined) {
            material.side = properties.side;
        }
        
        // 应用贴图（如果有）
        if (textureInfo && Object.keys(textureInfo).length > 0) {
            console.log(`🖼️ [创建预览材质] 应用 ${Object.keys(textureInfo).length} 个贴图`);
            
            for (const [textureType, textureData] of Object.entries(textureInfo)) {
                if (!textureData) {
                    console.log(`  ⚠️ ${textureType}: 贴图数据为空`);
                    continue;
                }
                
                console.log(`  🔍 处理贴图 ${textureType}:`, {
                    hasTexture: !!textureData.texture,
                    hasImage: !!textureData.image,
                    hasUrl: !!textureData.url,
                    textureType: textureData.texture?.constructor?.name,
                    imageType: textureData.image?.constructor?.name
                });
                
                // ✅ 优先使用保存的Three.js Texture对象
                if (textureData.texture && textureData.texture.image) {
                    console.log(`  ✅ ${textureType}: 使用原始Texture对象`, {
                        imageWidth: textureData.texture.image.width,
                        imageHeight: textureData.texture.image.height,
                        imageSrc: textureData.texture.image.src?.substring(0, 50)
                    });
                    material[textureType] = textureData.texture.clone();
                    material[textureType].needsUpdate = true;
                }
                // 如果有image对象，创建新的Texture
                else if (textureData.image) {
                    console.log(`  ✅ ${textureType}: 从image创建Texture`, {
                        imageWidth: textureData.image.width,
                        imageHeight: textureData.image.height
                    });
                    const tex = new THREE.Texture(textureData.image);
                    tex.needsUpdate = true;
                    
                    // 应用UV变换
                    if (textureData.repeat) {
                        tex.repeat.set(textureData.repeat.x, textureData.repeat.y);
                    }
                    if (textureData.offset) {
                        tex.offset.set(textureData.offset.x, textureData.offset.y);
                    }
                    if (textureData.rotation !== undefined) {
                        tex.rotation = textureData.rotation;
                    }
                    if (textureData.center) {
                        tex.center.set(textureData.center.x, textureData.center.y);
                    }
                    
                    material[textureType] = tex;
                }
                // 如果有URL但不是标记，尝试加载
                else if (textureData.url && !textureData.url.startsWith('[')) {
                    console.log(`  ⏳ ${textureType}: 异步加载贴图 from URL:`, textureData.url.substring(0, 50));
                    const textureLoader = new THREE.TextureLoader();
                    textureLoader.load(textureData.url, (tex) => {
                        material[textureType] = tex;
                        material.needsUpdate = true;
                        console.log(`  ✅ ${textureType}: 贴图加载完成`);
                    }, undefined, (err) => {
                        console.warn(`  ⚠️ ${textureType}: 贴图加载失败:`, err);
                    });
                }
                else {
                    console.log(`  ⚠️ ${textureType}: 跳过（无可用数据）`, {
                        url: textureData.url,
                        hasTexture: !!textureData.texture,
                        hasImage: !!textureData.image
                    });
                }
            }
        } else {
            console.warn(`⚠️ [创建预览材质] 没有贴图信息`);
        }
        
        material.needsUpdate = true;
        
        // 输出最终材质状态
        console.log(`✅ [创建预览材质] 材质创建完成:`, {
            hasMap: !!material.map,
            hasNormalMap: !!material.normalMap,
            hasBumpMap: !!material.bumpMap,
            hasAoMap: !!material.aoMap,
            color: `#${material.color.getHexString()}`,
            metalness: material.metalness,
            roughness: material.roughness
        });
        
        return material;
    }
    
    /**
     * 创建临时预览对象
     * @param {THREE.Material} material - 材质对象
     * @returns {THREE.Object3D} 预览对象
     */
    createTempPreviewObject(material) {
        // 创建一个简单的球体用于预览
        const geometry = new THREE.SphereGeometry(1, 64, 64);
        const mesh = new THREE.Mesh(geometry, material);
        mesh.name = 'PreviewMesh';
        
        return mesh;
    }
    
    /**
     * 取消上传 - 清空缓存并关闭
     */
    async cancelUpload() {
        try {
            // 🚀 设置取消标志，停止正在进行的并发上传
            this.uploadCancelled = true;
            
            // 执行步骤1的重置清空操作
            await this.clearCache();
            // 关闭审核对话框（如果打开的话）
            this.closeReviewDialog();
            // 关闭主对话框
            this.closeDialog();
        } catch (error) {
            console.error('❌ 取消上传失败:', error);
            this.showErrorMessage('取消失败：' + error.message);
        }
    }

    /**
     * 从阶段一完成上传
     */
    async completeUploadFromStageOne() {
        // 检查是否已解析材质
        if (!this.materialsAnalyzed || this.hasUnanalyzedModels) {
            alert('请先解析材质信息');
            return;
        }

        // 显示进度条
        this.showProgressBar();

        try {
            // 🎯 第一步：上传所有贴图到贴图库（去重、同名检查）
            this.updateProgress(0, '正在上传贴图...');
            const textureIdMap = await this.uploadAllTextures();
            
            // 🎯 第二步：创建新建材质并关联到模型mesh（使用贴图ID映射）
            this.updateProgress(30, '正在创建材质...');
            const newMaterialsMap = await this.createNewMaterials(textureIdMap);
            
            // 🎯 第三步：更新所有模型的mesh材质信息
            this.updateProgress(60, '正在更新模型配置...');
            const totalModels = this.cacheModels.length;
            for (let i = 0; i < totalModels; i++) {
                const model = this.cacheModels[i];
                
                // ⭐ 简化 meshMaterials：从完整对象 → 只保存 materialName
                const simplifiedMeshMaterials = {};
                for (const [meshName, meshData] of Object.entries(model.meshMaterials)) {
                    simplifiedMeshMaterials[meshName] = meshData.materialName;
                }
                
                // 保存简化后的数据到模型库
                await this.updateModelMeshMaterials(model.id, simplifiedMeshMaterials);
                
                // 更新进度：60% 到 85% 之间
                const progress = 60 + Math.floor((i + 1) / totalModels * 25);
                this.updateProgress(progress, `正在更新模型配置 (${i + 1}/${totalModels})...`);
            }
            
            // 🎯 第四步：调用后端完成上传
            const uploadLabel = this.uploadType === 'node' ? '节点库' : '模型库';
            this.updateProgress(85, `正在保存到${uploadLabel}...`);
            const response = await fetch(`/api/projects/${this.projectId}/models/complete-upload`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    uploadType: this.uploadType 
                })
            });
            
            const result = await response.json();
            
            if (result.success) {
                this.updateProgress(100, '上传完成！');
                
                // 显示成功提示消息
                const newMaterialCount = newMaterialsMap.size;
                const existingMaterialCount = this.materialUsageMap.size - newMaterialCount;
                const uploadedTextureCount = textureIdMap ? textureIdMap.size : 0;
                
                let message = `上传成功：${result.data.count} 个模型已添加到${uploadLabel}`;
                if (uploadedTextureCount > 0) {
                    message += `，${uploadedTextureCount} 个贴图已处理`;
                }
                if (newMaterialCount > 0) {
                    message += `，${newMaterialCount} 个新材质已创建`;
                }
                if (existingMaterialCount > 0) {
                    message += `，${existingMaterialCount} 个材质使用现有材质`;
                }
                
                // 等待一下让用户看到100%完成
                await new Promise(resolve => setTimeout(resolve, 500));
                
                this.hideProgressBar();
                this.showSuccessMessage(message);
                
                // 关闭审核对话框（如果打开的话）
                this.closeReviewDialog();
                
                // 关闭阶段一对话框
                this.closeDialog();
                
                // 立即更新模型库和模型管理库的模型数据
                await this.refreshModelLibrary();
            } else {
                throw new Error(result.error || '完成上传失败');
            }
        } catch (error) {
            console.error('❌ 完成上传失败:', error);
            this.hideProgressBar();
            this.showErrorMessage('上传失败：' + error.message);
        }
    }
    
    /**
     * 上传所有贴图到贴图库（统一处理，去重，检查同名）
     * @returns {Promise<Map>} 贴图名称 -> 贴图ID 的映射
     */
    async uploadAllTextures() {
        console.log('🎨 [上传贴图] 开始收集所有贴图...');
        
        // 1. 收集所有唯一的贴图（基于贴图名称去重）
        const textureMap = new Map(); // 贴图名称 -> 贴图数据
        
        for (const model of this.cacheModels) {
            if (!model.texturesMap) {
                continue;
            }
            
            // 处理Map类型或普通对象
            let texturesData = null;
            if (model.texturesMap instanceof Map) {
                texturesData = Array.from(model.texturesMap.values());
            } else if (typeof model.texturesMap === 'object') {
                texturesData = Object.values(model.texturesMap);
            }
            
            if (texturesData) {
                for (const materialTextures of texturesData) {
                    if (materialTextures && typeof materialTextures === 'object') {
                        // 从材质的贴图数据中提取所有贴图
                        this.collectTexturesFromMaterial(materialTextures, textureMap);
                    }
                }
            }
        }
        
        console.log(`📊 [上传贴图] 收集到 ${textureMap.size} 个唯一贴图`);
        
        if (textureMap.size === 0) {
            console.log('ℹ️ [上传贴图] 没有贴图需要上传');
            return new Map();
        }
        
        // 2. 获取贴图库中的所有贴图（检查同名）
        let libraryTextures = [];
        try {
            const response = await fetch(`/api/projects/${this.projectId}/textures`);
            const result = await response.json();
            if (result.success) {
                libraryTextures = result.data || [];
            }
        } catch (error) {
            console.warn('⚠️ [上传贴图] 获取贴图库失败，将上传所有贴图', error);
        }
        
        console.log(`📚 [上传贴图] 贴图库中已有 ${libraryTextures.length} 个贴图`);
        
        // 3. 上传贴图并建立名称->ID的映射
        const textureIdMap = new Map(); // 贴图名称 -> 贴图ID
        const totalTextures = textureMap.size;
        let currentIndex = 0;
        
        for (const [textureName, textureData] of textureMap) {
            currentIndex++;
            
            // 更新进度（0% - 30% 之间）
            const progress = Math.floor((currentIndex / totalTextures) * 30);
            this.updateProgress(progress, `正在处理贴图 (${currentIndex}/${totalTextures})...`);
            
            try {
                // 检查贴图库中是否已存在同名贴图
                const existingTexture = libraryTextures.find(libTex => {
                    const libName = libTex.originalFileName || libTex.fileName || libTex.name;
                    return libName === textureName;
                });
                
                if (existingTexture) {
                    // 同名贴图，复用现有贴图
                    console.log(`✅ [上传贴图] "${textureName}" 已存在，复用贴图 (${existingTexture.id})`);
                    textureIdMap.set(textureName, existingTexture.id);
                } else {
                    // 新贴图，上传到贴图库
                    console.log(`📤 [上传贴图] 上传新贴图 "${textureName}"...`);
                    
                    // 从textureData中获取image对象
                    const imageData = this.getTextureImageData(textureData);
                    
                    if (imageData) {
                        const textureId = await this.uploadTextureToLibrary(imageData, textureName);
                        
                        if (textureId) {
                            console.log(`  ✅ 上传成功: ${textureId}`);
                            textureIdMap.set(textureName, textureId);
                        } else {
                            console.warn(`  ⚠️ 上传失败: ${textureName}`);
                        }
                    } else {
                        console.warn(`  ⚠️ 无法获取贴图数据: ${textureName}`);
                    }
                }
            } catch (error) {
                console.error(`❌ [上传贴图] 处理贴图失败: ${textureName}`, error);
            }
        }
        
        console.log(`🎉 [上传贴图] 完成，共处理 ${textureIdMap.size} 个贴图`);
        
        return textureIdMap;
    }
    
    /**
     * 从材质贴图数据中收集贴图到Map（去重）
     * @param {Object} materialTextures - 材质的贴图数据
     * @param {Map} textureMap - 贴图Map（用于去重）
     */
    collectTexturesFromMaterial(materialTextures, textureMap) {
        const textureTypes = [
            'map', 'normalMap', 'bumpMap', 'aoMap', 
            'roughnessMap', 'metalnessMap', 'emissiveMap', 
            'alphaMap', 'displacementMap', 'specularMap', 'lightMap'
        ];
        
        for (const textureType of textureTypes) {
            const textureData = materialTextures[textureType];
            if (textureData) {
                // 提取贴图名称
                let textureName = '';
                
                if (textureData.name) {
                    textureName = textureData.name;
                } else if (textureData.texture && textureData.texture.name) {
                    textureName = textureData.texture.name;
                } else if (textureData.url) {
                    const urlParts = textureData.url.split('/');
                    textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                    textureName = textureName.split('?')[0];
                } else if (textureData.image && textureData.image.src) {
                    const urlParts = textureData.image.src.split('/');
                    textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                    textureName = textureName.split('?')[0];
                }
                
                if (!textureName) {
                    textureName = `texture_${textureType}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
                }
                
                // 使用贴图名称作为key去重
                if (!textureMap.has(textureName)) {
                    textureMap.set(textureName, textureData);
                }
            }
        }
    }
    
    /**
     * 从贴图数据中获取image数据（用于上传）
     * @param {Object} textureData - 贴图数据
     * @returns {HTMLImageElement|HTMLCanvasElement|null} image对象
     */
    getTextureImageData(textureData) {
        // 优先使用image对象
        if (textureData.image) {
            return textureData.image;
        }
        
        // 尝试从texture对象获取
        if (textureData.texture && textureData.texture.image) {
            return textureData.texture.image;
        }
        
        // 如果有url，尝试创建image对象
        if (textureData.url && !textureData.url.startsWith('data:')) {
            // 对于非DataURL的情况，返回null（无法直接上传）
            console.warn('⚠️ [获取贴图] 无法从URL创建image对象');
            return null;
        }
        
        return null;
    }
    
    /**
     * 创建新建材质并关联所有材质到mesh
     * @param {Map} textureIdMap - 贴图名称 -> 贴图ID 的映射
     * @returns {Map} 材质名称 -> 材质ID 的映射
     */
    async createNewMaterials(textureIdMap) {
        const newMaterialsMap = new Map();
        
        // 统计需要创建的新材质数量
        const materialsToCreate = Array.from(this.materialUsageMap.values()).filter(
            info => !info.matchingMaterial
        );
        const totalMaterialsToCreate = materialsToCreate.length;
        let createdCount = 0;
        
        // 遍历所有材质
        let currentIndex = 0;
        const totalMaterials = this.materialUsageMap.size;
        
        for (const [materialName, materialInfo] of this.materialUsageMap) {
            currentIndex++;
            let materialId = null;
            
            if (materialInfo.matchingMaterial) {
                // ✅ 新架构：如果有同名材质，使用 displayName 作为材质标识
                materialId = materialInfo.matchingMaterial.displayName;
                
                // 更新进度（0% - 40% 之间）
                const progress = Math.floor((currentIndex / totalMaterials) * 40);
                this.updateProgress(progress, `正在关联现有材质 (${currentIndex}/${totalMaterials})...`);
            } else {
                // 如果没有匹配的材质，需要创建新材质
                try {
                    createdCount++;
                    
                    // 更新进度（0% - 40% 之间）
                    const progress = Math.floor((currentIndex / totalMaterials) * 40);
                    this.updateProgress(
                        progress, 
                        `正在创建新材质: ${materialName} (${createdCount}/${totalMaterialsToCreate})...`
                    );
                    
                    // 🎯 从贴图ID映射中获取该材质的贴图ID
                    const textureIds = this.getMaterialTextureIdsFromMap(materialName, materialInfo, textureIdMap);
                    
                    // 🎯 从FBX提取的实际材质属性
                    const extractedProperties = this.getMaterialPropertiesFromUsage(materialInfo);
                    
                    // ✅ 新架构：不再生成 id 和 name，直接使用 displayName 作为唯一标识
                    const materialData = {
                        displayName: materialName,   // displayName 是唯一标识
                        ...this.getDefaultMaterialParams(),  // 先用默认值打底
                        ...extractedProperties,      // ✅ 覆盖为实际提取的属性
                        ...textureIds                // 添加贴图ID引用
                    };
                    
                    // 🎯 智能透明参数设置 - 关键修复！
                    this.applySmartTransparencySettings(materialData, materialName);
                    
                    console.log(`📋 [创建材质] ${materialName}`, {
                        提取的属性: extractedProperties,
                        最终材质数据: materialData
                    });
                    
                    // 调用材质库管理器创建材质（后端会自动处理重复情况）
                    const createdMaterial = await this.materialLibraryManager.createMaterial(materialData);
                    
                    if (createdMaterial && createdMaterial.displayName) {
                        // ✅ 使用 displayName 作为材质标识
                        materialId = createdMaterial.displayName;
                        newMaterialsMap.set(materialName, materialId);
                        
                        // 如果是已存在的材质，提示复用
                        if (createdMaterial.existed) {
                            console.log(`✅ 材质 "${materialName}" 已存在，复用已有材质`);
                        } else {
                            console.log(`✅ 创建新材质: ${materialName}`);
                        }
                    }
                } catch (error) {
                    console.error(`❌ 创建/获取材质失败: ${materialName}`, error);
                    // 如果失败，该材质将使用自身材质（materialId=0）
                }
            }
            
            // 🎯 更新所有使用该材质的mesh
            if (materialId) {
                console.log(`🔗 [关联材质] "${materialName}" (${materialId}) -> ${materialInfo.usages.length} 个Mesh`);
                
                for (const usage of materialInfo.usages) {
                    const model = this.cacheModels.find(m => m.id === usage.modelId);
                    if (model && model.meshMaterials && usage.meshId in model.meshMaterials) {
                        // ⭐ Cache阶段：只更新 materialName 字段，保持对象结构
                        model.meshMaterials[usage.meshId].materialName = materialId;
                    } else {
                        console.warn(`  ⚠️ [绑定失败] 找不到 Mesh: ${usage.meshName}`);
                        console.warn(`    模型存在: ${!!model}, meshMaterials存在: ${!!model?.meshMaterials}, Mesh存在: ${model ? (usage.meshId in model.meshMaterials) : false}`);
                        if (model && model.meshMaterials) {
                            console.warn(`    模型中的Mesh列表:`, Object.keys(model.meshMaterials));
                        }
                    }
                }
            }
        }
        
        return newMaterialsMap;
    }
    
    /**
     * 从贴图ID映射中获取材质的贴图ID
     * @param {string} materialName - 材质名称
     * @param {Object} materialInfo - 材质信息
     * @param {Map} textureIdMap - 贴图名称 -> 贴图ID 的映射
     * @returns {Object} 贴图ID映射对象
     */
    getMaterialTextureIdsFromMap(materialName, materialInfo, textureIdMap) {
        const textureIds = {};
        
        if (!textureIdMap || textureIdMap.size === 0) {
            console.log(`ℹ️ [获取贴图ID] 没有贴图ID映射`);
            return textureIds;
        }
        
        // ✅ 修复：遍历所有使用该材质的mesh，从texturesMap中通过meshName获取贴图
        for (const usage of materialInfo.usages) {
            const model = this.cacheModels.find(m => m.id === usage.modelId);
            if (!model || !model.texturesMap) {
                continue;
            }
            
            // ✅ 使用meshName（即usage.meshId）作为key查找贴图
            const meshName = usage.meshId; // meshId就是meshName
            let materialTextures = null;
            
            if (model.texturesMap instanceof Map) {
                materialTextures = model.texturesMap.get(meshName);
            } else if (typeof model.texturesMap === 'object') {
                materialTextures = model.texturesMap[meshName];
            }
            
            if (!materialTextures) {
                console.log(`⚠️ [获取贴图ID] 未找到mesh "${meshName}" 的贴图数据`);
                continue;
            }
            
            console.log(`🔍 [获取贴图ID] Mesh "${meshName}" (材质: "${materialName}") 的贴图...`);
            
            // 贴图类型映射
            const textureTypeMap = {
                'map': 'map',
                'normalMap': 'normalMap',
                'bumpMap': 'bumpMap',
                'aoMap': 'aoMap',
                'roughnessMap': 'roughnessMap',
                'metalnessMap': 'metalnessMap',
                'emissiveMap': 'emissiveMap',
                'specularMap': 'specularMap',
                'alphaMap': 'alphaMap',
                'displacementMap': 'displacementMap',
                'lightMap': 'lightMap'
            };
            
            for (const [textureType, fieldName] of Object.entries(textureTypeMap)) {
                const textureData = materialTextures[textureType];
                if (textureData) {
                    // 提取贴图名称
                    let textureName = '';
                    
                    if (textureData.name) {
                        textureName = textureData.name;
                    } else if (textureData.texture && textureData.texture.name) {
                        textureName = textureData.texture.name;
                    } else if (textureData.url) {
                        const urlParts = textureData.url.split('/');
                        textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                        textureName = textureName.split('?')[0];
                    } else if (textureData.image && textureData.image.src) {
                        const urlParts = textureData.image.src.split('/');
                        textureName = decodeURIComponent(urlParts[urlParts.length - 1]);
                        textureName = textureName.split('?')[0];
                    }
                    
                    // 从映射中获取贴图ID
                    if (textureName && textureIdMap.has(textureName)) {
                        const textureId = textureIdMap.get(textureName);
                        textureIds[fieldName] = textureId;
                        console.log(`  ✅ ${textureType}: ${textureName} -> ${textureId}`);
                        
                        // 保存UV变换信息
                        if (textureData.repeat) {
                            textureIds.uvRepeatX = textureData.repeat.x;
                            textureIds.uvRepeatY = textureData.repeat.y;
                        }
                        if (textureData.offset) {
                            textureIds.uvOffsetX = textureData.offset.x;
                            textureIds.uvOffsetY = textureData.offset.y;
                        }
                        if (textureData.rotation !== undefined) {
                            textureIds.uvRotation = textureData.rotation;
                        }
                        if (textureData.center) {
                            textureIds.uvCenterX = textureData.center.x;
                            textureIds.uvCenterY = textureData.center.y;
                        }
                    } else {
                        console.warn(`  ⚠️ ${textureType}: "${textureName}" 未在贴图ID映射中找到`);
                    }
                }
            }
            
            // 只处理第一个找到的模型（避免重复）
            break;
        }
        
        return textureIds;
    }
    
    /**
     * 上传材质的贴图到贴图库（已废弃，现在使用uploadAllTextures统一上传）
     * @deprecated 使用uploadAllTextures和getMaterialTextureIdsFromMap替代
     * @param {string} materialName - 材质名称
     * @param {Object} materialInfo - 材质信息
     * @returns {Promise<Object>} 贴图ID映射对象
     */
    async uploadMaterialTextures(materialName, materialInfo) {
        const textureIds = {};
        
        // 查找包含该材质贴图的模型
        for (const usage of materialInfo.usages) {
            const model = this.cacheModels.find(m => m.id === usage.modelId);
            if (!model || !model.meshMaterials[usage.meshId]) {
                continue;
            }
            
            const meshData = model.meshMaterials[usage.meshId];
            const textures = meshData.textures;
            
            if (!textures) {
                continue;
            }
            
            console.log(`🎨 [贴图上传] 处理材质 "${materialName}" 的贴图...`);
            
            // 上传各种类型的贴图
            const textureTypeMap = {
                'map': 'map',
                'normalMap': 'normalMap',
                'bumpMap': 'bumpMap',
                'aoMap': 'aoMap',
                'roughnessMap': 'roughnessMap',
                'metalnessMap': 'metalnessMap',
                'emissiveMap': 'emissiveMap',
                'specularMap': 'specularMap'
            };
            
            for (const [textureType, fieldName] of Object.entries(textureTypeMap)) {
                if (textures[textureType] && textures[textureType].image) {
                    const textureInfo = textures[textureType];
                    const textureName = textureInfo.name || `${materialName}_${textureType}`;
                    
                    console.log(`  📤 上传 ${textureType}: ${textureName}`);
                    
                    // 上传贴图
                    const textureId = await this.uploadTextureToLibrary(textureInfo.image, textureName);
                    
                    if (textureId) {
                        textureIds[fieldName] = textureId;
                        console.log(`  ✅ ${textureType} 上传成功: ${textureId}`);
                        
                        // 保存UV变换信息
                        if (textureInfo.repeat) {
                            textureIds.uvRepeatX = textureInfo.repeat.x;
                            textureIds.uvRepeatY = textureInfo.repeat.y;
                        }
                        if (textureInfo.offset) {
                            textureIds.uvOffsetX = textureInfo.offset.x;
                            textureIds.uvOffsetY = textureInfo.offset.y;
                        }
                        if (textureInfo.rotation !== undefined) {
                            textureIds.uvRotation = textureInfo.rotation;
                        }
                        if (textureInfo.center) {
                            textureIds.uvCenterX = textureInfo.center.x;
                            textureIds.uvCenterY = textureInfo.center.y;
                        }
                    }
                }
            }
            
            // 只处理第一个找到的模型（避免重复上传）
            break;
        }
        
        return textureIds;
    }
    
    /**
     * 生成符合规则的材质name（只包含字母、数字、下划线和连字符）
     * @param {string} originalName - 原始材质名称（可能包含中文）
     * @returns {string} 符合规则的name
     */
    generateSafeMaterialName(originalName) {
        // 使用拼音或transliteration会比较复杂，这里使用简单的方案：
        // 1. 移除所有非法字符
        // 2. 如果结果为空或太短，使用原始名称的哈希值确保唯一性
        
        // 方法1: 尝试保留ASCII字符
        let safeName = originalName.replace(/[^a-zA-Z0-9_-]/g, '_');
        
        // 🔧 修复：如果名称为空或只有下划线/连字符，使用原始名称的简单哈希
        if (!safeName || /^[_-]+$/.test(safeName)) {
            // 生成一个基于原始名称的简单哈希（确保相同名称得到相同结果）
            let hash = 0;
            for (let i = 0; i < originalName.length; i++) {
                const char = originalName.charCodeAt(i);
                hash = ((hash << 5) - hash) + char;
                hash = hash & hash; // Convert to 32bit integer
            }
            // 使用绝对值并转换为36进制字符串
            const hashStr = Math.abs(hash).toString(36);
            safeName = `material_${hashStr}`;
        }
        
        // 确保名称不以数字开头（某些系统可能有这个限制）
        if (/^\d/.test(safeName)) {
            safeName = 'mat_' + safeName;
        }
        
        // 限制长度
        if (safeName.length > 50) {
            safeName = safeName.substring(0, 50);
        }
        
        return safeName;
    }
    
    /**
     * 从材质使用信息中获取实际的材质属性
     * @param {Object} materialInfo - 材质使用信息
     * @returns {Object} 材质属性对象
     */
    getMaterialPropertiesFromUsage(materialInfo) {
        // 从第一个使用该材质的mesh中获取properties
        if (!materialInfo.usages || materialInfo.usages.length === 0) {
            return {};
        }
        
        const firstUsage = materialInfo.usages[0];
        const model = this.cacheModels.find(m => m.id === firstUsage.modelId);
        
        if (!model || !model.meshMaterials || !model.meshMaterials[firstUsage.meshId]) {
            return {};
        }
        
        const meshData = model.meshMaterials[firstUsage.meshId];
        const props = meshData.properties;
        
        if (!props) {
            return {};
        }
        
        // 返回所有属性（用于显示）+ 转换为材质库格式（用于保存）
        const materialParams = {};
        
        // 材质类型
        if (props.type) {
            materialParams.type = props.type;
        }
        
        // 基础颜色
        if (props.color) {
            materialParams.color = props.color;
            
            // 🎯 检查是否为黑色透明材质（常见问题）
            if (props.color === '#000000' && (props.transparent || props.alphaTest > 0)) {
                console.log(`⚠️ [颜色检查] 发现黑色透明材质，可能导致透明区域显示黑色: ${materialInfo.usages[0]?.meshName || '未知'}`);
            }
        }
        
        // PBR 属性
        if (props.metalness !== undefined) {
            materialParams.metalness = props.metalness;
        }
        if (props.roughness !== undefined) {
            materialParams.roughness = props.roughness;
        }
        
        // 发光属性
        if (props.emissive) {
            materialParams.emissive = props.emissive;
        }
        if (props.emissiveIntensity !== undefined) {
            materialParams.emissiveIntensity = props.emissiveIntensity;
        }
        
        // 高光属性（Phong材质）
        if (props.specular) {
            materialParams.specular = props.specular;
        }
        if (props.shininess !== undefined) {
            materialParams.shininess = props.shininess;
        }
        
        // 透明度相关属性
        if (props.opacity !== undefined) {
            materialParams.opacity = props.opacity;
        }
        if (props.transparent !== undefined) {
            materialParams.transparent = props.transparent;
        }
        if (props.alphaTest !== undefined) {
            materialParams.alphaTest = props.alphaTest;
        }
        if (props.depthWrite !== undefined) {
            materialParams.depthWrite = props.depthWrite;
        }
        if (props.depthTest !== undefined) {
            materialParams.depthTest = props.depthTest;
        }
        
        // 双面渲染
        if (props.side !== undefined) {
            // 保留原始值用于显示
            materialParams.side = props.side;
            // 转换为布尔值用于材质库
            materialParams.doubleSided = (props.side === 2); // THREE.DoubleSide = 2
        }
        
        // AO 和凹凸强度
        if (props.aoMapIntensity !== undefined) {
            materialParams.aoMapIntensity = props.aoMapIntensity;
            materialParams.aoIntensity = props.aoMapIntensity; // 兼容材质库命名
        }
        if (props.bumpScale !== undefined) {
            materialParams.bumpScale = props.bumpScale;
        }
        
        // 法线贴图强度
        if (props.normalScale) {
            materialParams.normalScale = props.normalScale;
        }
        
        // 其他高级属性
        if (props.flatShading !== undefined) {
            materialParams.flatShading = props.flatShading;
        }
        if (props.wireframe !== undefined) {
            materialParams.wireframe = props.wireframe;
        }
        if (props.vertexColors !== undefined) {
            materialParams.vertexColors = props.vertexColors;
        }
        
        return materialParams;
    }
    
    /**
     * 获取默认材质参数
     */
    getDefaultMaterialParams() {
        return {
            color: '#ffffff',
            metalness: 0.5,
            roughness: 0.5,
            emissive: '#000000',
            emissiveIntensity: 0,
            opacity: 1.0,
            aoIntensity: 1.0,
            bumpScale: 0.0,
            doubleSided: false,
            // 🎯 透明相关参数 - 关键修复！
            transparent: false,     // 默认不透明
            alphaTest: 0,          // 默认不使用alpha测试
            depthWrite: true,      // 默认启用深度写入
            depthTest: true,       // 默认启用深度测试
            // 贴图参数
            map: null,
            normalMap: null,
            bumpMap: null,
            aoMap: null,
            roughnessMap: null,
            metalnessMap: null,
            alphaMap: null,        // 🎯 透明贴图
            emissiveMap: null,
            displacementMap: null,
            specularMap: null,
            lightMap: null
        };
    }

    /**
     * 刷新模型库
     */
    async refreshModelLibrary() {
        try {
            if (this.uploadType === 'node') {
                // 刷新节点库
                console.log('🔄 刷新节点库...');
                
                // 1. 刷新节点库侧边栏面板（总是刷新数据）
                if (window.populateNodeList && typeof window.populateNodeList === 'function') {
                    await window.populateNodeList();
                    console.log('✅ 节点库侧边栏已刷新');
                } else {
                    console.warn('⚠️ window.populateNodeList 不存在');
                }
                
                // 2. 刷新节点库管理界面（如果打开的话）
                const manageModal = document.getElementById('model-library-manage-modal');
                if (manageModal && manageModal.classList.contains('active')) {
                    if (window.loadNodeManageList && typeof window.loadNodeManageList === 'function') {
                        await window.loadNodeManageList();
                        console.log('✅ 节点库管理界面已刷新');
                    } else {
                        console.warn('⚠️ window.loadNodeManageList 不存在');
                    }
                }
            } else {
                // 刷新模型库
                console.log('🔄 刷新模型库...');
                
                // 1. 刷新模型库侧边栏面板（总是刷新数据）
                if (window.populateModelList && typeof window.populateModelList === 'function') {
                    await window.populateModelList();
                    console.log('✅ 模型库侧边栏已刷新');
                } else {
                    console.warn('⚠️ window.populateModelList 不存在');
                }
                
                // 2. 刷新模型管理界面（如果打开的话）
                const manageModal = document.getElementById('model-library-manage-modal');
                if (manageModal && manageModal.classList.contains('active')) {
                    if (window.loadModelManageList && typeof window.loadModelManageList === 'function') {
                        await window.loadModelManageList();
                        console.log('✅ 模型库管理界面已刷新');
                    } else {
                        console.warn('⚠️ window.loadModelManageList 不存在');
                    }
                }
            }
        } catch (error) {
            console.error('❌ 刷新模型库失败:', error);
        }
    }

    /**
     * 关闭对话框
     */
    closeDialog() {
        if (this.dialogElement) {
            this.dialogElement.remove();
            this.dialogElement = null;
        }
        this.currentStage = 0;
    }

    /**
     * 格式化文件大小
     */
    formatFileSize(bytes) {
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
    }

    /**
     * 显示成功消息提示
     */
    showSuccessMessage(message) {
        this.showToast(message, 'success');
    }

    /**
     * 显示错误消息提示
     */
    showErrorMessage(message) {
        this.showToast(message, 'error');
    }

    /**
     * 显示Toast提示
     */
    showToast(message, type = 'success') {
        // 创建Toast元素
        const toast = document.createElement('div');
        toast.className = `upload-toast upload-toast-${type}`;
        toast.innerHTML = `
            <div class="toast-icon">${type === 'success' ? '✅' : '❌'}</div>
            <div class="toast-message">${message}</div>
        `;
        
        // 添加样式
        toast.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: ${type === 'success' ? '#4CAF50' : '#f44336'};
            color: white;
            padding: 15px 30px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            z-index: 10000;
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 14px;
            animation: slideDown 0.3s ease-out;
        `;
        
        document.body.appendChild(toast);
        
        // 3秒后自动消失
        setTimeout(() => {
            toast.style.animation = 'slideUp 0.3s ease-out';
            setTimeout(() => {
                toast.remove();
            }, 300);
        }, 3000);
    }

    /**
     * 显示进度条
     */
    showProgressBar() {
        // 如果已存在进度条，先移除
        if (this.progressBarElement) {
            this.progressBarElement.remove();
        }

        // 创建进度条容器
        const progressBar = document.createElement('div');
        progressBar.className = 'upload-progress-overlay';
        progressBar.innerHTML = `
            <div class="upload-progress-container">
                <div class="progress-header">
                    <div class="progress-icon">📤</div>
                    <h3 class="progress-title">正在上传模型...</h3>
                </div>
                <div class="progress-bar-wrapper">
                    <div class="progress-bar">
                        <div class="progress-bar-fill" style="width: 0%"></div>
                    </div>
                    <div class="progress-percentage">0%</div>
                </div>
                <div class="progress-status">准备中...</div>
            </div>
        `;

        document.body.appendChild(progressBar);
        this.progressBarElement = progressBar;

        // 禁用完成上传按钮
        const btnComplete = document.getElementById('btn-complete-upload');
        if (btnComplete) {
            btnComplete.disabled = true;
        }
    }

    /**
     * 更新进度条
     * @param {number} progress - 进度百分比 (0-100)
     * @param {string} status - 状态文本
     */
    updateProgress(progress, status) {
        if (!this.progressBarElement) return;

        const progressFill = this.progressBarElement.querySelector('.progress-bar-fill');
        const progressPercentage = this.progressBarElement.querySelector('.progress-percentage');
        const progressStatus = this.progressBarElement.querySelector('.progress-status');

        if (progressFill) {
            progressFill.style.width = `${progress}%`;
        }
        if (progressPercentage) {
            progressPercentage.textContent = `${Math.round(progress)}%`;
        }
        if (progressStatus) {
            progressStatus.textContent = status;
        }
    }

    /**
     * 隐藏进度条
     */
    hideProgressBar() {
        if (this.progressBarElement) {
            this.progressBarElement.style.animation = 'fadeOut 0.3s ease-out';
            setTimeout(() => {
                if (this.progressBarElement) {
                    this.progressBarElement.remove();
                    this.progressBarElement = null;
                }
            }, 300);
        }

        // 恢复完成上传按钮
        const btnComplete = document.getElementById('btn-complete-upload');
        if (btnComplete) {
            btnComplete.disabled = false;
        }
    }
    
    /**
     * 智能设置透明参数 - Alpha值优先透明策略
     * @param {Object} materialData - 材质数据
     * @param {string} materialName - 材质名称
     */
    applySmartTransparencySettings(materialData, materialName) {
        const materialNameLower = materialName.toLowerCase();
        const isRoadLine = materialNameLower.includes('road') || 
                          materialNameLower.includes('line') || 
                          materialNameLower.includes('马路') || 
                          materialNameLower.includes('线');
        
        // 🎯 Alpha值优先策略：检查是否有alpha透明设置
        const hasAlphaMap = !!materialData.alphaMap;
        const hasTransparentTexture = !!(materialData.map && this.isTransparentTexture(materialData.map));
        const hasOpacityTransparency = materialData.opacity < 1.0;
        const hasAlphaTestTransparency = materialData.alphaTest > 0;
        const hasTransparentFlag = materialData.transparent;
        
        const hasAnyTransparency = hasAlphaMap || hasTransparentTexture || hasOpacityTransparency || 
                                  hasAlphaTestTransparency || hasTransparentFlag;
        
        if (hasAnyTransparency) {
            console.log(`🎭 [Alpha优先透明策略] 检测到透明材质: ${materialName}`, {
                hasAlphaMap,
                hasTransparentTexture,
                hasOpacityTransparency,
                hasAlphaTestTransparency,
                hasTransparentFlag,
                isRoadLine,
                原始颜色: materialData.color,
                原始opacity: materialData.opacity
            });
            
            // 🔑 关键策略：透明区域直接透明，不做任何颜色计算
            if (hasAlphaMap || hasTransparentTexture || hasOpacityTransparency || hasAlphaTestTransparency) {
                // 透明材质：基础颜色设为纯白，确保透明区域完全透明，不参与颜色混合
                materialData.color = '#ffffff';
                console.log(`   🎨 透明区域直接透明策略：基础颜色设为纯白，透明区域不计算贴图颜色通道`);
            }
            
            // 🎯 根据透明类型设置参数
            if (hasOpacityTransparency) {
                // opacity透明模式：以opacity值为准
                materialData.transparent = true;
                materialData.alphaTest = 0;
                materialData.depthWrite = false;
                materialData.depthTest = true;
                console.log(`   🔧 opacity透明模式：opacity=${materialData.opacity}`);
                
            } else if (hasAlphaTestTransparency || hasAlphaMap || hasTransparentTexture) {
                // alphaTest透明模式：硬边透明
                if (isRoadLine) {
                    // 马路线特殊处理
                    materialData.transparent = false;
                    materialData.alphaTest = Math.max(0.1, materialData.alphaTest || 0.5);
                    materialData.depthWrite = true;
                    materialData.depthTest = true;
                    console.log(`   🛣️ 马路线alphaTest模式：alphaTest=${materialData.alphaTest}`);
                } else {
                    // 其他物体的alphaTest模式
                    materialData.transparent = false;
                    materialData.alphaTest = materialData.alphaTest || 0.5;
                    materialData.depthWrite = true;
                    materialData.depthTest = true;
                    console.log(`   🔧 alphaTest透明模式：alphaTest=${materialData.alphaTest}`);
                }
                
            } else if (hasTransparentFlag) {
                // 纯transparent模式
                materialData.transparent = true;
                materialData.alphaTest = 0;
                materialData.depthWrite = false;
                materialData.depthTest = true;
                console.log(`   🔧 transparent模式：基础颜色白色化`);
            }
            
            console.log(`   ✅ Alpha优先透明策略应用完成`);
            console.log(`   💡 原理：alpha值主导透明效果，基础颜色和贴图RGB影响降低`);
            
        } else {
            // 🎯 非透明材质：保持原有逻辑
            materialData.transparent = false;
            materialData.alphaTest = 0;
            materialData.depthWrite = true;
            materialData.depthTest = true;
            materialData.opacity = 1.0;
            console.log(`🎨 [非透明材质] ${materialName}: 保持不透明状态`);
        }
    }
    
    /**
     * 检查贴图名称是否为透明格式
     * @param {string} textureName - 贴图名称
     * @returns {boolean} 是否为透明格式
     */
    isTransparentTexture(textureName) {
        if (!textureName || typeof textureName !== 'string') {
            return false;
        }
        
        const lowerName = textureName.toLowerCase();
        return lowerName.match(/\.(png|tga|webp)$/);
    }
}

// 导出单例（支持不同的uploadType）
let twoStageUploadManagerInstance = null;

export function getTwoStageUploadManager(projectId, projectPath, uploadType = 'model') {
    // 每次创建新实例，因为uploadType可能不同
    twoStageUploadManagerInstance = new TwoStageUploadManager(projectId, projectPath, uploadType);
    return twoStageUploadManagerInstance;
}

// 暴露到全局
window.getTwoStageUploadManager = getTwoStageUploadManager;


